On 2011-04-11, at 5:15 PM, Marc Feeley wrote:
On 2011-04-07, at 3:44 PM, Maxime Chevalier-Boisvert wrote:
As I said, the code segment can be made read-write-execute with a
call to mprotect.
On which operating systems can you guarantee that this is feasible?
mprotect is in the same family of functions as mmap, which Tachyon currently uses to allocate machine code blocks. So it will very likely work on all the operating systems that we care about. Note that the equivalent function on WIN32 is called VirtualProtect. These facilities have existed for over 10 years in various operating systems.
I wrote the program attached below to verify that this technique works on the operating systems we care about most (GNU/Linux, Mac OS X, and Windows).
Marc
Following Maxime's comments today, I have tested the loading time with and without the execution of the code (0% means the code is loaded but not executed, and 100% means that in addition to loading the code, each instruction in all of the code is executed once).
I have tested on Linux (baro) and my MacBook Pro, with images from 1MB to 128MB, using a local disk. The assembly time in seconds is measured and the time in milliseconds to load and execute the code. The "no copy" approach directly calls the code in the "text" area. The "with copy" approach allocates a machine code block, copies the code from the "text" area to the machine code block (using memcpy), and then calls the machine code block (the copied code would have to be patched with the correct absolute addresses, but that is ignored for now, which means the time for the "with copy" case is optimistic).
Here are the results:
| Linux (baro) | "load+execution" time (ms) | image asm 0% executed 100% executed | size time no with no with | (s) copy copy copy copy | 1MB 0.257 1 2 1 2 | 2MB 0.447 1 3 1 3 | 4MB 0.841 1 5 3 6 | 8MB 1.602 1 10 5 11 | 16MB 3.054 1 18 9 22 | 32MB 5.796 1 33 17 31 | 64MB 11.154 1 57 25 63 | 128MB 22.014 1 109 55 125 | | MacOS X (macro) | "load+execution" time (ms) | image asm 0% executed 100% executed | size time no with no with | (s) copy copy copy copy | 1MB 0.849 3 5 3 5 | 2MB 0.865 3 7 6 9 | 4MB 1.847 3 14 8 21 | 8MB 3.483 4 20 12 23 | 16MB 7.050 5 39 21 46 | 32MB 14.127 7 77 38 84 | 64MB 27.779 15 151 74 168 | 128MB 58.125 35 300 149 328
The "no copy" approach is always a win, whether the code is executed or not.
When all the code is executed (which is very unlikely in practice) the "no copy" approach is about twice as fast as "with copy". Obviously this becomes significant with large images. Interestingly, on Linux, the "no copy" approach has a constant cost for the loading (1 millisecond) regardless of the size of the image, and the cost goes up proportionately to how much of the image is actually executed (more precisely the number of pages of code that are touched by the execution). On MacOS X, when 0% is executed the "no copy" approach is up to 11 times faster than the "with copy" approach, and when 10% is executed "no copy" is consistently 2 times faster.
Note that the Firefox application on my Mac is 69MB. So the 64MB case in my tables is probably representative of a full browser implemented in JavaScript.
I've attached the files I have used on Linux.
Marc
P.S. In this test I have used the assembler to generate data in the "text" section. The generated image, in assembler, looks like this:
.text .globl _code_start .globl _code_end .align 12 _code_start: .byte 144,144,144,144,144,144,144,144,144,144,144,144,144,144,144,144,144,144,144,144,184 .long _code_start .byte 144,144,144,144,144,144,144,144,144,144,144,144,144,144,144,184 .long _code_start .byte 144,144,144,144,144,144,144,144,144,144,144,144,144,144,144,184 .long _code_start .byte 144,144,144,144,144,144,144,144,144,144,144,144,144,144,144,184 .long _code_start .byte 144,144,144,144,144,144,144,144,144,144,144,144,144,144,144 .byte 195 .align 12 _code_end:
144 is the encoding of "nop" 184 is the first byte of the encoding of "movl $_code_start,%eax" 195 is the encoding of "ret"
I have put references to _code_start inside the code just to show how absolute addresses can be put in the code such that the operating system linker/loader will fill in those parts with the correct absolute address.