5 ms·
On the second point, the OP is complaining more a about the lack of a way to directly access anything smaller than 4 bytes, i.e., that there are no 1 or 2 byte
by BeeOnRope 7y ago
On the second point, the OP is complaining more a about the lack of a way to directly access anything smaller than 4 bytes, i.e., that there are no 1 or 2 byte reads or writes.
In addition to being slow for e.g. text processing, you couldn't implement a modern memory model like Java's or C++ on it, without serious compromises, due to the inability to make non-interfering writes of adjacent elements.
- dragontamer 7y ago> In addition to being slow for e.g. text processing, you couldn't implement a modern memory model like Java's or C++ on it, without serious compromises, due to the inability to make non-interfering writes of adjacent elements. But consider that the modern model accounts for 64-byte (aka: 512-bit) cachelines. Any reads or writes to a 64-byte cacheline is going to be false-shared between cores. From a performance point of view, you cannot make non-interfering writes of adjacent elements in the modern memory model! If you're dealing with single-threaded code, then bit-mask instructions for registers should be sufficient to emulate the ability. I can see that bit-masking every character instruction would be slower, but it would certainly get the job done.
- BeeOnRope 7y agoI don't think the performance thing is relevant here. First you have to be able to implement the model correctly. That means non-interfering writes. 99.99% of those writes will never involve any sharing (false or otherwise) and will perform just fine. Not being able to do byte-granular writes means you simply can't implement things like this at all unless you: 1) Decide that "char" will be 32 bits (i.e., make a byte on your system 4x larger than usual). 2) Do some locking around each narrow write (or perhaps a series of them) and ensure everyone does the same. So I am not really following your argument with regard to false sharing. I don't know what you mean by "the modern model accounts for 64-byte (aka: 512-bit) cachelines". I don't know of any (software) memory model that even mentions cache lines. Hardware memory models don't lean heavily on them either, although they do get mentioned (e.g., on x86, Intel provides additional guarantees for some misaligned reads that do not cross a cache line, and AMD provides different guarantees).
- dragontamer 7y ago> 1) Decide that "char" will be 32 bits (i.e., make a byte on your system 4x larger than usual). 2) Do some locking around each narrow write (or perhaps a series of them) and ensure everyone does the same. Or 3) Decide that for performance reasons, reads/writes to an array (even a char* array or string) would be locked in 64-byte chunks at a minimum. My point with false-sharing is that "true" byte-granularity comes with severe performance penalties on modern x86 systems. Its a thing that you want to avoid. If one thread is reading / writing to char myString[0], while a 2nd thread is reading/writing to myString[63], your memory will ping-pong between the two threads (because x86 shares 64-bytes at a time between caches). -------- I guess your point is that the CPU will "do the right thing, even if they're forced to do it slowly" in this case. So I guess you're right about that. Your code CAN be written as multithreaded + byte granular, its just going to be slow in practice. I guess my point is that the high-performance programmer needs to understand this situation anyway, and that high-performance programmers have developed methodologies to avoid false-sharing. Any high-performance data-structure needs to understand false-sharing. You really shouldn't be doing things like reading/writing to myString[0] and myString[63] in two separate threads.
- BeeOnRope 7y ago> Or 3) Decide that for performance reasons, reads/writes to an array (even a char* array or string) would be locked in 64-byte chunks at a minimum. First note that this doesn't have anything to do with arrays specifically: any type of adjacent elements (in a struct, loose on the stack, whatever) have this problem. That said, I have no idea how you would implement this suggestion. E.g., how would you compile the following very simple function? void write1(char *c) { *c = 1; } > My point with false-sharing is that "true" byte-granularity comes with severe performance penalties on modern x86 systems. Its a thing that you want to avoid. It obviously does not! x86 has true byte granularity and loads and stores are very fast, just as fast as any other size. Just because the possibility of false sharing exists, doesn't mean it somehow slows down byte writes. False sharing doesn't even have anything to do with byte writes: it affects any size of access. So I am really not following your argument. To summarize my claim: - Modern memory models generally require that writing some "object" (which could be a primitive value like a char, int, whatever) not interfere with adjacent objects. - This applies to all writes, unless perhaps the compiler can prove the data never escaped and cannot be shared, so in practice it means that you can only efficiently write at the smallest granularity supported by hardware. - This means that you pretty much need 32-bit char on platforms where the smallest hardware write is 32 bits, or just give up on supporting the memory model. None of that has anything to do with false sharing really. Not even really performance: we are just trying to get it to be correct. Saying "well you don't want to concurrently read nearby elements anyways" doesn't mean much because it doesn't lead to a practical way to implement the language (unless I am missing something). > I guess your point is that the CPU will "do the right thing, even if they're forced to do it slowly" in this case. So I guess you're right about that. Your code CAN be written as multithreaded + byte granular, its just going to be slow in practice. Not at all. My point has nothing to do with false sharing. You brought up false sharing. My point is that the memory model is defined not prohibit behavior X, and platforms that need to do a large-word RMW to modify smaller elements can't effectively prohibit X. This is true if you have false sharing or no false sharing or a small amount of false sharing. This is true even if you don't start any threads. This is true also for scenarios of "true sharing", e.g., where the other thread wants to share data (i.e., after reading the adjacent element maybe it is going to read the other one too). > I guess my point is that the high-performance programmer needs to understand this situation anyway It has nothing to do with the (application) programmer or performance. It is more fundamental than that: how does the compiler writer write a compiler that produces correct code under the memory model?