This chapter describes how Native Client applications handle memory allocation, how to measure a program's memory usage, and how to improve an application's performance by customizing its memory management.
When a Native Client module is loaded into a browser tab it will run in a separate process, distinct from the render process for the tab itself. Chrome creates a fixed-size address space for the new process; 1 GB of memory for an ARM or x86-32 target architecture and 4 GB for an x86-64 target. This space contains the usual segments for data, text, stack, heap, and mmap. The address space is private for that single instance of the module and it is not shared with instances of the same or any other Native Client module. There is no Native Client API to examine the state of an instance's address space.
Memory management library calls handle fine-grained requests by divvying up the available memory in the address space, removing and returning bytes in the free pool. When the pool is exhausted the allocator asks for more memory from the service runtime. When this happens Native Client will call for a gross allocation rounded up to a multiple of 64K bytes, regardless of the actual size of the original request.
Pointers in Native Client code (e.g., sizeof(void*)) are always 4 bytes, in order to support portability.
The Native Client SDK toolchain offers two C libraries for building a Native Client module: newlib and glibc. By default, each of these libraries uses a different version of the dlmalloc allocator and they will behave differently for different kinds of usage patterns. It is difficult to predict whether choosing one library or the other will significantly affect an application's performance with respect to memory management. The choice of allocators will probably not be a determining factor for most developers when selecting which toolchain to use. However, in some cases developers with deep knowledge of these libraries may have a good reason to choose one over the other. You know who you are.
Do not use sbrk to manipulate the heap space. The allocators use sbrk and your effort could interfere and corrupt the state of the heap.
Memory access validation
There is no runtime memory access validation in Native Client, but the sandbox service runtime will trap illegal instructions. Access errors can cause hardware exceptions, for example invalid memory accesses into guard memory pages or into reserved but uncommitted memory pages when dereferencing a bad pointer.
Some operating systems (notably Linux) have an overcommit policy for memory allocation. In rare cases this can cause an application to crash when it attempts to access non-existent memory that was over-committed during a previous, apparently successful allocation. On other systems such an allocation might fail outright much sooner with an explicit malloc failure return that could be detected.
Memory management issues can contribute to poor application performance. This section describes some tools and techniques you can use to measure and monitor memory usage and overall performance.
Use profiling and debugging tools
See the Debugging page for information about general debugging tools that are available.
Home-grown code profiling
If the rendering rate is unacceptably low or your application seems to be slow to update or respond to user input, it may be useful to examine your code's use of memory manager calls, especially the malloc function and the new operator. Unfortunately, the Chrome profiling tools cannot reach into the process running the Native Client instance. You can instrument your code yourself to track memory usage. For example, add a counter to all important uses of new in your C++ methods and accumulate and printout the total count for each render cycle.
To minimize the performance-robbing overhead in Native Client memory management calls, you can write your own memory manager to handle critical application resources:
- Consider pre-allocating and maintaining free-lists of objects that can be re-used.
- Manage your own in-process heap space. First allocate a private heap using the default C library calls, and then manage that heap with the same library or another one of your choice. Some possible options are TCMalloc, dlmalloc, and TLSF.