Recent

Author Topic: Benchmark aligned vs unaligned memory access  (Read 827 times)

LemonParty

  • Hero Member
  • *****
  • Posts: 537
Benchmark aligned vs unaligned memory access
« on: June 03, 2026, 04:47:56 pm »
Today is a new benchmark.
Now we testing aligned vs unaligned memory access (read and write). I know you want to know what are the penalties for such things in code.

There are four functions that we test:
1. ProceedBufferR for aligned reads;
2. ProceedBufferR for unaligned reads;
3. ProceedBufferW for aligned writes;
4. ProceedBufferW for unaligned writes.

As before we got results for 32 KB buffer and 1024 * 1024 elements.
Compiled with FPC trunk, with O4.
Results from Intel Core Ultra 7 258V:
Code: Pascal  [Select][+][-]
  1. 2048 ELEMENTS BY 100 RESULTS
  2. R Aligned   : 2009
  3. R Unaligned : 1794
  4. W Aligned   : 1431
  5. W Unaligned : 1699
  6. 1048576 ELEMENTS BY 4 RESULTS
  7. R Aligned   : 59942
  8. R Unaligned : 45328
  9. W Aligned   : 47531
  10. W Unaligned : 48410
  11.  
  12. 2048 ELEMENTS BY 100 RESULTS
  13. R Aligned   : 2010
  14. R Unaligned : 1790
  15. W Aligned   : 1452
  16. W Unaligned : 1774
  17. 1048576 ELEMENTS BY 4 RESULTS
  18. R Aligned   : 57081
  19. R Unaligned : 49017
  20. W Aligned   : 55540
  21. W Unaligned : 49495
  22.  
  23. 2048 ELEMENTS BY 100 RESULTS
  24. R Aligned   : 2005
  25. R Unaligned : 1794
  26. W Aligned   : 1453
  27. W Unaligned : 1711
  28. 1048576 ELEMENTS BY 4 RESULTS
  29. R Aligned   : 58108
  30. R Unaligned : 45920
  31. W Aligned   : 51376
  32. W Unaligned : 57593
  33.  
  34. 2048 ELEMENTS BY 100 RESULTS
  35. R Aligned   : 3904
  36. R Unaligned : 1792
  37. W Aligned   : 1484
  38. W Unaligned : 1791
  39. 1048576 ELEMENTS BY 4 RESULTS
  40. R Aligned   : 61919
  41. R Unaligned : 45102
  42. W Aligned   : 47802
  43. W Unaligned : 50868
  44.  
  45. 2048 ELEMENTS BY 100 RESULTS
  46. R Aligned   : 2007
  47. R Unaligned : 1793
  48. W Aligned   : 2262
  49. W Unaligned : 1704
  50. 1048576 ELEMENTS BY 4 RESULTS
  51. R Aligned   : 57229
  52. R Unaligned : 44652
  53. W Aligned   : 57620
  54. W Unaligned : 47896

Results from Raspberry Pi 5:
Code: Pascal  [Select][+][-]
  1. 2048 ELEMENTS BY 100 RESULTS
  2. R Aligned   : 16409
  3. R Unaligned : 25298
  4. W Aligned   : 14602
  5. W Unaligned : 16445
  6. 1048576 ELEMENTS BY 4 RESULTS
  7. R Aligned   : 336992
  8. R Unaligned : 467549
  9. W Aligned   : 290157
  10. W Unaligned : 210943
  11.  
  12. 2048 ELEMENTS BY 100 RESULTS
  13. R Aligned   : 16379
  14. R Unaligned : 25438
  15. W Aligned   : 14532
  16. W Unaligned : 16447
  17. 1048576 ELEMENTS BY 4 RESULTS
  18. R Aligned   : 375964
  19. R Unaligned : 431169
  20. W Aligned   : 268620
  21. W Unaligned : 245286
  22.  
  23. 2048 ELEMENTS BY 100 RESULTS
  24. R Aligned   : 10235
  25. R Unaligned : 15808
  26. W Aligned   : 9119
  27. W Unaligned : 10280
  28. 1048576 ELEMENTS BY 4 RESULTS
  29. R Aligned   : 234160
  30. R Unaligned : 344658
  31. W Aligned   : 248997
  32. W Unaligned : 211223
  33.  
  34. 2048 ELEMENTS BY 100 RESULTS
  35. R Aligned   : 10217
  36. R Unaligned : 16031
  37. W Aligned   : 9237
  38. W Unaligned : 10419
  39. 1048576 ELEMENTS BY 4 RESULTS
  40. R Aligned   : 234592
  41. R Unaligned : 339352
  42. W Aligned   : 222450
  43. W Unaligned : 210605
  44.  
  45. 2048 ELEMENTS BY 100 RESULTS
  46. R Aligned   : 15339
  47. R Unaligned : 23825
  48. W Aligned   : 13623
  49. W Unaligned : 15592
  50. 1048576 ELEMENTS BY 4 RESULTS
  51. R Aligned   : 332540
  52. R Unaligned : 480895
  53. W Aligned   : 302644
  54. W Unaligned : 211050

Conclusions:
1. On x86 you could win significant speed with a small chunks when you do aligned writes;
2. On x86 there is no big difference between reads/writes, only exception see above;
3. On ARM unaligned reads is slow (or maybe this is a problem of Raspberry CPUs).
4. On large chunks of data the bottleneck is a memory bandwidth.
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

Thaddy

  • Hero Member
  • *****
  • Posts: 19273
  • Glad to be alive.
Re: Benchmark aligned vs unaligned memory access
« Reply #1 on: June 03, 2026, 04:56:50 pm »
1. ARM processors are indeed still vulnerable for alignment, not only Raspberry Pi's (any model)
2. Both modern Intel and AMD processors solve the alignment during 1st stage caching. Processor level.
3. Older Intels and less so AMD's are vulnerable for alignment issues. On intel this used to be extreme with SSE/SSE2.

All this is documented over the years, just google a bit. Nothing new.
What is recommended, though, is taking care of alignment in generic software for Intel and AMD and mandatory for ARM.

Your tests are only valid for YOUR processors and these are not "generic".Such tests should be run over many different processor models to be of any value. Sorry, you wasted your time. With the exception of ARM, that is correct.
« Last Edit: June 03, 2026, 05:01:22 pm by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

LemonParty

  • Hero Member
  • *****
  • Posts: 537
Re: Benchmark aligned vs unaligned memory access
« Reply #2 on: June 03, 2026, 08:07:31 pm »
Quote
All this is documented over the years, just google a bit.
This information is not on the surface, you should dive into some complex articles to find it.

Quote
Your tests are only valid for YOUR processors and these are not "generic".
By doing such test we can see the tendency. And the tendency doesn't change in few generations of CPUs. By the way if someone has a big difference in results of test he/she can publish it here.
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

LeP

  • Sr. Member
  • ****
  • Posts: 347
Re: Benchmark aligned vs unaligned memory access
« Reply #3 on: June 03, 2026, 09:19:20 pm »
Result with Lazarus 4.6 FPC 3.2.2, I9 14900HX
Code: [Select]
2048 ELEMENTS BY 100 RESULTS
R Aligned   : 1387
R Unaligned : 795
W Aligned   : 601
W Unaligned : 711
1048576 ELEMENTS BY 4 RESULTS
R Aligned   : 39173
R Unaligned : 33160
W Aligned   : 30631
W Unaligned : 26848
I don't repeat 'cause the results are near the same.
Un Sistema per domarli, un IDE per trovarli, un codice per ghermirli e nel framework incatenarli.
An operating system to tame them, an IDE to find them, a code to catch them and in the framework chain them.

MathMan

  • Hero Member
  • *****
  • Posts: 518
Re: Benchmark aligned vs unaligned memory access
« Reply #4 on: June 03, 2026, 09:57:38 pm »
Quote
All this is documented over the years, just google a bit.
This information is not on the surface, you should dive into some complex articles to find it.

Quote
Your tests are only valid for YOUR processors and these are not "generic".
By doing such test we can see the tendency. And the tendency doesn't change in few generations of CPUs. By the way if someone has a big difference in results of test he/she can publish it here.

@LemonParty - unfortunately your benchmark program is not sufficient for these types of measurements. However using my own benchmark environment on a ZEN 3 core I can provide the following raw data for aligned/misaligned AVX2 (32 byte) read/writes for 16 KByte chunks (half of L1 cache size).

reads:
Code: Pascal  [Select][+][-]
  1. function name | number of UInt64 | byte offset from 64 byte aligned mem | avg clock cycles | min clock cycles
  2.  
  3. lLimbsTest;2048;0;230395;average;257,37;minimum;220
  4. lLimbsTest;2048;1;229833;average;262,69;minimum;220
  5. lLimbsTest;2048;2;228973;average;270,85;minimum;220
  6. lLimbsTest;2048;3;228670;average;273,75;minimum;220
  7. lLimbsTest;2048;4;228717;average;273,30;minimum;220
  8. lLimbsTest;2048;5;228446;average;275,89;minimum;240
  9. lLimbsTest;2048;6;229531;average;265,54;minimum;240
  10. lLimbsTest;2048;7;229530;average;265,56;minimum;240
  11. lLimbsTest;2048;8;227414;average;285,82;minimum;220
  12. lLimbsTest;2048;9;227205;average;287,85;minimum;240
  13. lLimbsTest;2048;10;227026;average;289,58;minimum;240
  14. lLimbsTest;2048;11;226846;average;291,33;minimum;260
  15. lLimbsTest;2048;12;227974;average;280,42;minimum;240
  16. lLimbsTest;2048;13;226658;average;293,16;minimum;240
  17. lLimbsTest;2048;14;227912;average;281,02;minimum;240
  18. lLimbsTest;2048;15;227898;average;281,15;minimum;260
  19. lLimbsTest;2048;16;228206;average;278,19;minimum;260
  20. lLimbsTest;2048;17;227999;average;280,18;minimum;240
  21. lLimbsTest;2048;18;227360;average;286,34;minimum;260
  22. lLimbsTest;2048;19;227205;average;287,84;minimum;240
  23. lLimbsTest;2048;20;227982;average;280,35;minimum;260
  24. lLimbsTest;2048;21;227031;average;289,53;minimum;220
  25. lLimbsTest;2048;22;226927;average;290,54;minimum;260
  26. lLimbsTest;2048;23;227033;average;289,51;minimum;240
  27. lLimbsTest;2048;24;226768;average;292,09;minimum;240
  28. lLimbsTest;2048;25;226977;average;290,05;minimum;240
  29. lLimbsTest;2048;26;228535;average;275,04;minimum;240
  30. lLimbsTest;2048;27;228302;average;277,27;minimum;260
  31. lLimbsTest;2048;28;228490;average;275,47;minimum;260
  32. lLimbsTest;2048;29;227252;average;287,39;minimum;240
  33. lLimbsTest;2048;30;226863;average;291,16;minimum;220
  34. lLimbsTest;2048;31;228384;average;276,48;minimum;240
  35. lLimbsTest;2048;32;228556;average;274,84;minimum;260
  36. lLimbsTest;2048;33;227313;average;286,80;minimum;240
  37. lLimbsTest;2048;34;227222;average;287,68;minimum;220
  38. lLimbsTest;2048;35;227089;average;288,97;minimum;240
  39. lLimbsTest;2048;36;228459;average;275,77;minimum;240
  40. lLimbsTest;2048;37;226916;average;290,65;minimum;240
  41. lLimbsTest;2048;38;227147;average;288,41;minimum;260
  42. lLimbsTest;2048;39;227239;average;287,52;minimum;220
  43. lLimbsTest;2048;40;227056;average;289,29;minimum;220
  44. lLimbsTest;2048;41;228334;average;276,96;minimum;240
  45. lLimbsTest;2048;42;227072;average;289,13;minimum;240
  46. lLimbsTest;2048;43;227004;average;289,80;minimum;240
  47. lLimbsTest;2048;44;227263;average;287,28;minimum;260
  48. lLimbsTest;2048;45;227069;average;289,16;minimum;240
  49. lLimbsTest;2048;46;227206;average;287,83;minimum;240
  50. lLimbsTest;2048;47;228495;average;275,42;minimum;240
  51. lLimbsTest;2048;48;228539;average;275,00;minimum;240
  52. lLimbsTest;2048;49;228687;average;273,58;minimum;240
  53. lLimbsTest;2048;50;228944;average;271,13;minimum;240
  54. lLimbsTest;2048;51;227183;average;288,06;minimum;220
  55. lLimbsTest;2048;52;227248;average;287,43;minimum;240
  56. lLimbsTest;2048;53;227228;average;287,62;minimum;240
  57. lLimbsTest;2048;54;227067;average;289,18;minimum;260
  58. lLimbsTest;2048;55;227302;average;286,91;minimum;260
  59. lLimbsTest;2048;56;228573;average;274,68;minimum;240
  60. lLimbsTest;2048;57;228276;average;277,52;minimum;260
  61. lLimbsTest;2048;58;228492;average;275,45;minimum;240
  62. lLimbsTest;2048;59;228403;average;276,30;minimum;260
  63. lLimbsTest;2048;60;228568;average;274,73;minimum;260
  64. lLimbsTest;2048;61;228502;average;275,35;minimum;240
  65. lLimbsTest;2048;62;228628;average;274,14;minimum;240
  66. lLimbsTest;2048;63;227153;average;288,35;minimum;220

writes
Code: Pascal  [Select][+][-]
  1. function name | number of UInt64 | byte offset from 64 byte aligned mem | avg clock cycles | min clock cycles
  2.  
  3. lLimbsTest;2048;0;231117;average;276,41;minimum;240
  4. lLimbsTest;2048;1;221649;average;368,83;minimum;320
  5. lLimbsTest;2048;2;223343;average;351,71;minimum;320
  6. lLimbsTest;2048;3;222845;average;356,71;minimum;320
  7. lLimbsTest;2048;4;224234;average;342,82;minimum;300
  8. lLimbsTest;2048;5;222474;average;360,46;minimum;320
  9. lLimbsTest;2048;6;222190;average;363,33;minimum;320
  10. lLimbsTest;2048;7;222036;average;364,89;minimum;320
  11. lLimbsTest;2048;8;223501;average;350,13;minimum;320
  12. lLimbsTest;2048;9;222436;average;360,84;minimum;320
  13. lLimbsTest;2048;10;222448;average;360,72;minimum;320
  14. lLimbsTest;2048;11;222521;average;359,99;minimum;320
  15. lLimbsTest;2048;12;224248;average;342,68;minimum;320
  16. lLimbsTest;2048;13;222062;average;364,63;minimum;320
  17. lLimbsTest;2048;14;222459;average;360,61;minimum;300
  18. lLimbsTest;2048;15;222427;average;360,93;minimum;320
  19. lLimbsTest;2048;16;223600;average;349,14;minimum;300
  20. lLimbsTest;2048;17;221272;average;372,66;minimum;320
  21. lLimbsTest;2048;18;222016;average;365,09;minimum;300
  22. lLimbsTest;2048;19;222246;average;362,76;minimum;320
  23. lLimbsTest;2048;20;223784;average;347,30;minimum;280
  24. lLimbsTest;2048;21;222186;average;363,37;minimum;320
  25. lLimbsTest;2048;22;222240;average;362,83;minimum;320
  26. lLimbsTest;2048;23;222210;average;363,13;minimum;320
  27. lLimbsTest;2048;24;223860;average;346,54;minimum;300
  28. lLimbsTest;2048;25;222302;average;362,20;minimum;320
  29. lLimbsTest;2048;26;222221;average;363,02;minimum;320
  30. lLimbsTest;2048;27;222291;average;362,30;minimum;320
  31. lLimbsTest;2048;28;223834;average;346,81;minimum;300
  32. lLimbsTest;2048;29;222339;average;361,83;minimum;320
  33. lLimbsTest;2048;30;222268;average;362,54;minimum;320
  34. lLimbsTest;2048;31;221948;average;365,78;minimum;300
  35. lLimbsTest;2048;32;229114;average;295,33;minimum;260
  36. lLimbsTest;2048;33;221900;average;366,27;minimum;320
  37. lLimbsTest;2048;34;222724;average;357,93;minimum;320
  38. lLimbsTest;2048;35;222289;average;362,33;minimum;320
  39. lLimbsTest;2048;36;223950;average;345,64;minimum;320
  40. lLimbsTest;2048;37;222595;average;359,23;minimum;320
  41. lLimbsTest;2048;38;221843;average;366,85;minimum;320
  42. lLimbsTest;2048;39;222273;average;362,49;minimum;320
  43. lLimbsTest;2048;40;224118;average;343,98;minimum;280
  44. lLimbsTest;2048;41;222423;average;360,97;minimum;300
  45. lLimbsTest;2048;42;222254;average;362,68;minimum;320
  46. lLimbsTest;2048;43;222519;average;360,01;minimum;320
  47. lLimbsTest;2048;44;224250;average;342,66;minimum;300
  48. lLimbsTest;2048;45;222523;average;359,96;minimum;320
  49. lLimbsTest;2048;46;222469;average;360,51;minimum;300
  50. lLimbsTest;2048;47;222345;average;361,76;minimum;320
  51. lLimbsTest;2048;48;223734;average;347,80;minimum;300
  52. lLimbsTest;2048;49;222262;average;362,60;minimum;320
  53. lLimbsTest;2048;50;222228;average;362,94;minimum;320
  54. lLimbsTest;2048;51;222357;average;361,64;minimum;300
  55. lLimbsTest;2048;52;224299;average;342,17;minimum;300
  56. lLimbsTest;2048;53;222655;average;358,64;minimum;320
  57. lLimbsTest;2048;54;222295;average;362,27;minimum;320
  58. lLimbsTest;2048;55;222402;average;361,19;minimum;320
  59. lLimbsTest;2048;56;224233;average;342,83;minimum;320
  60. lLimbsTest;2048;57;222762;average;357,55;minimum;320
  61. lLimbsTest;2048;58;222498;average;360,22;minimum;320
  62. lLimbsTest;2048;59;222303;average;362,19;minimum;320
  63. lLimbsTest;2048;60;224295;average;342,21;minimum;320
  64. lLimbsTest;2048;61;222639;average;358,80;minimum;300
  65. lLimbsTest;2048;62;222670;average;358,48;minimum;300
  66. lLimbsTest;2048;63;222632;average;358,86;minimum;320

One can see a slight impact on reads and a substantial impact on writes.

Figures will of course vary if run on a different core architecture or different sizes (going through cache hierarchy).

Cheers,
MathMan
« Last Edit: June 03, 2026, 10:02:36 pm by MathMan »

Seenkao

  • Hero Member
  • *****
  • Posts: 761
    • New ZenGL.
Re: Benchmark aligned vs unaligned memory access
« Reply #5 on: June 03, 2026, 10:11:54 pm »
Я посмотрел ваш тест. Это специфичный тест, который может работать в обоих случаях одинаково на системах x86 и может быть будет выигрыш при выравнивании на системах ARM (но не уверен).

Все обращения к данным происходят последовательно и все линии кэша могут последовательно друг за другом заполняться и читать/писать данные последовательно.

Если бы данные читались не последовательно, а как-то выборочно, то при выравнивании может быть будет какой-то дополнительный эффект. А в данном случае - это воля случая.

Я, делая эмулятор Nes, привёл протестил пример работы с кэшем. Потому что мне надо было улучшить работоспособность эмулятора для слабых систем. Ссылку на видео приложу ниже. В самом видео есть код, не помню, выкладывал я этот код или нет, возможно его можно найти в последней версии ZenGL. Если вы будете заинтересованы (видео на русском, уж извиняюсь).

---------------------------------------------------------------
Google translate:
I looked at your test. It's a specific test that might work the same in both cases on x86 systems, and it might benefit from alignment on ARM systems (but I'm not sure).

All data accesses occur sequentially, and all cache lines can be filled sequentially, reading and writing data sequentially.

If the data were read selectively rather than sequentially, then alignment might have some additional effect. But in this case, it's just a matter of chance.

When I was making the Nes emulator, I tested an example of cache handling. Because I needed to improve the emulator's performance on weaker systems. I'll include a link to the video below. The video itself contains code; I don't remember whether I posted it or not, but it might be found in the latest version of ZenGL. If you are interested (the video is in Russian, I apologize).

------------------------------------------------------------

http://www.youtube.com/watch?v=y81OGn0eXhc
https://rutube.ru/video/cad104f7c32c967ec72208c8edb51b6c/ (для тех у кого ютуб не работает / For those who can't access YouTube).
« Last Edit: June 03, 2026, 10:13:45 pm by Seenkao »
Rus: Стремлюсь к созданию минимальных и достаточно быстрых приложений.

Eng: I strive to create applications that are minimal and reasonably fast.
Working on ZenGL

runewalsh

  • Full Member
  • ***
  • Posts: 126
Re: Benchmark aligned vs unaligned memory access
« Reply #6 on: June 03, 2026, 10:34:34 pm »
Your benchmark(s?) are completely meaningless.

Want a real trick though? Run on x86:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc} {$modeswitch advancedrecords}
  2. uses
  3.         System.Diagnostics;
  4.  
  5. const
  6.         PageSize = 4096;
  7.         Misalignment = 1;
  8.  
  9. var
  10.         buf: pointer;
  11.         sw: TStopWatch;
  12.         total_100k, i: uint32;
  13.         aligned: boolean;
  14.  
  15. begin
  16.         buf := pointer((PtrUint(GetMem((PageSize - 1) + Misalignment + sizeof(uint32))) + Misalignment + (PageSize - 1)) and PtrUint(-PageSize));
  17.         for aligned := true downto false do
  18.         begin
  19.                 sw := sw.StartNew; total_100k := 0;
  20.                 repeat
  21.                         for i := 0 to 99999 do pUint32(buf)^ := 0;
  22.                         inc(total_100k);
  23.                 until sw.ElapsedMilliseconds >= 1000;
  24.                 sw.Stop;
  25.                 if aligned then write('Single-page uint32 write: ') else write('Cross-page uint32 write: ');
  26.                 writeln(uint64(total_100k) * 100000 / sw.ElapsedMilliseconds * 1000 / 1000000:0:1, ' Mops/sec');
  27.                 if aligned then dec(buf, Misalignment);
  28.         end;
  29. {$ifdef windows} readln; {$endif}
  30. end.

LeP

  • Sr. Member
  • ****
  • Posts: 347
Re: Benchmark aligned vs unaligned memory access
« Reply #7 on: June 03, 2026, 11:07:18 pm »
@LemonParty
Really, the code is not right at all. It is wrong.

Try to activate debug info and range chacking ... there is an (lot of) AV.
Un Sistema per domarli, un IDE per trovarli, un codice per ghermirli e nel framework incatenarli.
An operating system to tame them, an IDE to find them, a code to catch them and in the framework chain them.

MathMan

  • Hero Member
  • *****
  • Posts: 518
Re: Benchmark aligned vs unaligned memory access
« Reply #8 on: June 03, 2026, 11:39:18 pm »
@runewalsh

I'm pretty sure I know the result without compiling and running. You're trying to force a page miss - which will work if your system is running on small-pages.

But in what way do you think it generally invalidates a benchmark that is designed to investigate for an application that repeatedly manipulates buffered data? I'm first to admit that if your application is hit by continuous page misses any reasoning about data alignment can become obsolete. But to generally dismiss benchmarking due to that? Or did I misunderstand?

Curious,
MathMen

PS - I admit that my initial response was more of a knee-jerk reaction to @Thaddy's statement that misaligned access has been solved for read & write.
« Last Edit: June 03, 2026, 11:49:09 pm by MathMan »

runewalsh

  • Full Member
  • ***
  • Posts: 126
Re: Benchmark aligned vs unaligned memory access
« Reply #9 on: June 04, 2026, 12:23:41 am »
This benchmark measures too simple operations in too short loops. With such simple operations and such short loops, 20% (or even 2×) of a difference are nothing more than fluctuations.

(And indeed, running it under heaptrc shows memory corruption. Loops should have Count - 1 as their limit.)

No, I’m not forcing a page miss — I’m forcing a cross-page write that has 20× penalty which is more convincing and less prone to random factors.

MathMan

  • Hero Member
  • *****
  • Posts: 518
Re: Benchmark aligned vs unaligned memory access
« Reply #10 on: June 04, 2026, 12:57:15 am »
This benchmark measures too simple operations in too short loops. With such simple operations and such short loops, 20% (or even 2×) of a difference are nothing more than fluctuations.

(And indeed, running it under heaptrc shows memory corruption. Loops should have Count - 1 as their limit.)

I mentioned that and used my own, which does address these and things like warm-up (on laptops), cache pre-definition, etc.

No, I’m not forcing a page miss — I’m forcing a cross-page write that has 20× penalty which is more convincing and less prone to random factors.

Yepp, my fault.

LemonParty

  • Hero Member
  • *****
  • Posts: 537
Re: Benchmark aligned vs unaligned memory access
« Reply #11 on: June 04, 2026, 08:44:38 am »
Fixed Count to Count - 1.

Quote
No, I’m not forcing a page miss — I’m forcing a cross-page write that has 20× penalty which is more convincing and less prone to random factors.
That's great but we testing a real world scenario where you have a buffer of some data and you proceed it one by one. Writing exactly at cross-page gap every time is not likely to happen in real world.

Quote
This benchmark measures too simple operations in too short loops.
The first category of 32 KB is choosen because it fits into L1 cache. That is required to see how fast operations are done. In previous benchmark https://forum.lazarus.freepascal.org/index.php/topic,74085.0.html we found that SIMD could be 10x-12x times faster than linear methods, but memory bandwidth cut down this speed to only 4x.
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

Thaddy

  • Hero Member
  • *****
  • Posts: 19273
  • Glad to be alive.
Re: Benchmark aligned vs unaligned memory access
« Reply #12 on: June 04, 2026, 08:45:08 am »
(And indeed, running it under heaptrc shows memory corruption. Loops should have Count - 1 as their limit.)
And buffers should be free'd? ... O:-) :D Your code also leaks...
objects are fine constructs. You can even initialize them with constructors.

LemonParty

  • Hero Member
  • *****
  • Posts: 537
Re: Benchmark aligned vs unaligned memory access
« Reply #13 on: June 04, 2026, 09:47:23 am »
Seenkao, can you give a link to the function mentioned in the video? It is hard to read it from the screen.
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

Seenkao

  • Hero Member
  • *****
  • Posts: 761
    • New ZenGL.
Re: Benchmark aligned vs unaligned memory access
« Reply #14 on: June 04, 2026, 01:06:07 pm »
Sorry, comment are in Russian.
Source code in ZenGL 4.20.
Code: Pascal  [Select][+][-]
  1. type
  2.   // если с помощью шейдеров можно удалять тексели из текстуры, то для шейдеров не обязательно будет делать "вычитание спрайтов"
  3.   // с помощью "dataOut", значит и сохранять эти данные не обязательно.
  4.   pzTVRAMManager = record
  5.     // бланк видеопамяти в виде двух текстур для знакогенераторов.
  6.     // первые данные, указатель на полную видеостраницу (по умолчанию нулевая)
  7.     // вторые данные - именно две текстуры (видеопамять $0000-$0FFF и $1000-$1FFF).
  8.     VRAMTexture: array of array[0..1] of Cardinal;
  9.     // первое значение - номер в менеджере. Второе - банк видепамяти ($0000-$0FFF и $1000-$1FFF).
  10.     // Третье массив передаваемый в текстуру.
  11.     dataOut: array of array [0..1, 0..65535] of Cardinal;      // 65535 * 4            не надо здесь менять на другое измерение, тут для цвета сделан размер.
  12.     // почему 65535, а не 49151 ?   Делаю четвёртые данные для вычитания.
  13.     sprite: Cardinal;
  14.     Count: Cardinal;
  15.   end;
  16.  
  17. var
  18.   // менеджер копий видео-памяти, отображено в виде текстур (нужен если будет много сменяющихся бланков видеопамяти ).
  19.   managerVRAM: pzTVRAMManager;
  20.   // это данные как в текстурах, нужно это будет или нет? Например для отладки?
  21.   // vData: array [0..5, 0..4095] of Byte;
  22.   Texture0FrameCoord: array [0..1025] of zglTTextureCoord;
  23.   // пока для одного спрайта/тайла (3 текстуры).
  24.   Vertices: array [0..17] of zglGLESTVertex;
  25.   VertColor: array [0..17] of zglTColor;
  26.  
  27. ...
  28.  
  29. // Для понимания. Данная функция может "сжирать" очень много процессорного времени. Она используется в играх, где происхоит постоянное перестроение
  30. // данных изображения и происходит вызов почти на каждый кадр (а может и на каждый).
  31. // Я сделал уже немало для ускорения работы данной функции, и думаю надо будет ещё сильнее ускорять её по возможности. Но проблема в том, что созданную
  32. // текстуру ещё надо передать в память видеокарты, а это будет всегда "узким горлышком".
  33. function texCreateCHRROM2(data1, data2: PByteArray; ID1, ID2: Cardinal): Boolean;
  34. var
  35.   i, j2, j3, x, y, numData: pzInteger;
  36.  
  37.   //  Есть желание, тестируйте. В дальнейшем этот код удалю, оставляю только самый "перспективный".
  38.   procedure MovData1(data: PByteArray);
  39.   var
  40.     j: Integer;
  41.     n1, n2, nn1, nn2, XandY: Cardinal;
  42.     PmanVRAM: PCardinal;
  43.   begin
  44.     { Not optimization }
  45.     PmanVRAM := @managerVRAM.dataOut[managerVRAM.Count - 1, numData];
  46.     // обнуляем данные
  47.     FillChar(PmanVRAM^, 65536 * 4, 0);
  48.     i := 4095;
  49.     y := 0;
  50.  
  51.     while i >= 0 do                       // 4096
  52.     begin
  53.       x := 120;
  54.       j3 := 16;                             // 16 спрайтов в линии
  55.       while j3 > 0 do
  56.       begin
  57.         j2 := 8;                              // всего 8 линий в спрайте
  58.         while j2 > 0 do
  59.         begin
  60.           n1 := data^[i - 8];                  // брать надо два байта (составляют одну линию).
  61.           n2 := data^[i];
  62.           // пробегаемся по данным в памяти
  63.           for j := 7 downto 0 do              // одна линия спрайта.
  64.           begin
  65.             nn1 := ((n1 shr j) and 1);// * $FFFFFFFF;
  66.             nn2 := ((n2 shr j) and 1);
  67.             XandY := x + y * 128;
  68.             PmanVRAM := @managerVRAM.dataOut[managerVRAM.Count - 1, numData, XandY];
  69.             if nn1 = 1 then      // удостоверится, что здесь именно байт браться будет.
  70.             begin
  71.               // в четвёртую текстуру дублирую запись, чтоб не делать лишних проверок.
  72.               PmanVRAM^ := $FFFFFFFF;
  73.               if nn2 = 1 then
  74.               begin
  75.                 // третья текстура
  76.                 inc(PmanVRAM, 16384);
  77.                 PmanVRAM^ := $FFFFFFFF;
  78.               end
  79.               else begin
  80.                 // первая текстура
  81.                 inc(PmanVRAM, 49152);
  82.                 PmanVRAM^ := $FFFFFFFF;
  83.               end;
  84.             end
  85.             else
  86.               if nn2 = 1 then
  87.               begin
  88.                 // здесь продублировано.
  89.                 PmanVRAM^ := $FFFFFFFF;
  90.                 // вторая текстура
  91.                 inc(PmanVRAM, 32768);
  92.                 PmanVRAM^ := $FFFFFFFF;
  93.               end;
  94.  
  95.             inc(x);
  96.           end;
  97.  
  98.           x := x - 8;
  99.           inc(y);                               // переходим к следующей линии
  100.           dec(j2);                              // указываем что уменьшилось количество линий в спрайте.
  101.           dec(i);                               // смещаем
  102.         end;
  103.         dec(j3);
  104.         dec(y, 8);                              // вернуть на начальную линию
  105.         dec(x, 8);                              // сдвинуть икс на сделующий спрайт
  106.         dec(i, 8);                              // здесь смещение через спрайт (обработали уже два спрайта).
  107.       end;
  108.       y := y + 8;                               // если 16 спрайтов готово, то перейти на следующие спрайты
  109.     end;
  110.   end;
  111.  
  112.   procedure MovData2(data: PByteArray);
  113.   var
  114.     j: Integer;
  115.     PmanVRAM: PCardinal;
  116.     n1, n2, nn1, nn2, XandY: Cardinal;
  117.   begin
  118.     { First optimization }
  119.     //PmanVRAM := @managerVRAM.dataOut[managerVRAM.Count - 1, numData];
  120.     i := 4095;
  121.     y := 0;
  122.  
  123.     while i >= 0 do                       // 4096
  124.     begin
  125.       x := 120;
  126.       j3 := 16;                             // 16 спрайтов в линии
  127.       while j3 > 0 do
  128.       begin
  129.         j2 := 8;                              // всего 8 линий в спрайте
  130.         while j2 > 0 do
  131.         begin
  132.           n1 := data^[i - 8];                  // брать надо два байта (составляют одну линию).
  133.           n2 := data^[i];
  134.           // пробегаемся по данным в памяти
  135.           for j := 7 downto 0 do              // одна линия спрайта.
  136.           begin
  137.             nn1 := -((n1 shr j) and 1);
  138.             nn2 := -((n2 shr j) and 1);
  139.             XandY := x + y * 128;
  140.             PmanVRAM := @managerVRAM.dataOut[managerVRAM.Count - 1, numData, XandY];
  141.             //XandY2 := 16384 + XandY1;
  142.             //XandY3 := 32768 + XandY1;
  143.             //XandY4 := 49152 + XandY1;
  144.  
  145.             PmanVRAM^ := nn1 or nn2;
  146.             inc(PmanVRAM, 16384);
  147.             PmanVRAM^ := nn1 and nn2;
  148.             inc(PmanVRAM, 16384);
  149.             PmanVRAM^ := nn2;
  150.             inc(PmanVRAM, 16384);
  151.             PmanVRAM^ := nn1;
  152.  
  153.             inc(x);
  154.           end;
  155.  
  156.           x := x - 8;
  157.           inc(y);                               // переходим к следующей линии
  158.           dec(j2);                              // указываем что уменьшилось количество линий в спрайте.
  159.           dec(i);                               // смещаем
  160.         end;
  161.         dec(j3);
  162.         dec(y, 8);                              // вернуть на начальную линию
  163.         dec(x, 8);                              // сдвинуть икс на сделующий спрайт
  164.         dec(i, 8);                              // здесь смещение через спрайт (обработали уже два спрайта).
  165.       end;
  166.       y := y + 8;                               // если 16 спрайтов готово, то перейти на следующие спрайты
  167.     end;
  168.   end;
  169.  
  170.   procedure MovData3(data: PByteArray);
  171.   var
  172.     j, z, j2, j3: Integer;
  173.     XandY: Cardinal;
  174.     m1, m2: array[0..7] of Cardinal;
  175.     PmanVRAM: PCardinal;
  176.   begin
  177.     { Second optimization }
  178.     i := 4095;
  179.     y := 0;
  180.  
  181.     while i >= 0 do                       // 4096
  182.     begin
  183.       x := 120;
  184.       for j3 := 15 downto 0 do                // 16 спрайтов в линии
  185.       begin
  186.         for j2 := 7 downto 0 do               // всего 8 линий в спрайте
  187.         begin
  188.           XandY := x + y * 128;
  189.           m1[0] := byte(data^[i - 8]);                  // брать надо два байта (составляют одну линию).
  190.           m2[0] := data^[i];
  191.           m1[1] := -((m1[0] shr 6) and 1);                                      // заполняю массив данными
  192.           m2[1] := -((m2[0] shr 6) and 1);
  193.           m1[2] := -((m1[0] shr 5) and 1);
  194.           m2[2] := -((m2[0] shr 5) and 1);
  195.           m1[3] := -((m1[0] shr 4) and 1);
  196.           m2[3] := -((m2[0] shr 4) and 1);
  197.           m1[4] := -((m1[0] shr 3) and 1);
  198.           m2[4] := -((m2[0] shr 3) and 1);
  199.           m1[5] := -((m1[0] shr 2) and 1);
  200.           m2[5] := -((m2[0] shr 2) and 1);
  201.           m1[6] := -((m1[0] shr 1) and 1);
  202.           m2[6] := -((m2[0] shr 1) and 1);
  203.           m1[7] := -(m1[0] and 1);
  204.           m2[7] := -(m2[0] and 1);
  205.           m1[0] := -((m1[0] shr 7) and 1);
  206.           m2[0] := -((m2[0] shr 7) and 1);
  207.  
  208.           PmanVRAM := @managerVRAM.dataOut[managerVRAM.Count - 1, numData, XandY];
  209.           // пробегаемся по данным в памяти
  210.           // четвёртая текстура
  211.           PmanVRAM^ := m1[0] or m2[0];                                          // первая линия        1
  212.           for z := 1 to 7 do
  213.           begin
  214.             inc(PmanVRAM);
  215.             PmanVRAM^ := m1[z] or m2[z];                                        // первая линия        2 3 4 5 6 7 8
  216.           end;
  217.           // третья текстура
  218.           inc(PmanVRAM, 16384 - 7);
  219.           PmanVRAM^ := m1[0] and m2[0];                                         // вторая линия
  220.           for z := 1 to 7 do
  221.           begin
  222.             inc(PmanVRAM);
  223.             PmanVRAM^ := m1[z] and m2[z];                                       // вторая линия
  224.           end;
  225.           // вторая текстура
  226.           inc(PmanVRAM, 16384 - 7);
  227.           PmanVRAM^ := m2[0];                                                   // третья линия
  228.           for z := 1 to 7 do
  229.           begin
  230.             inc(PmanVRAM);
  231.             PmanVRAM^ := m2[z];                                                 // третья линия
  232.           end;
  233.           // первая текстура
  234.           inc(PmanVRAM, 16384 - 7);
  235.           PmanVRAM^ := m1[0];                                                   // четвёртая линия
  236.           for z := 1 to 7 do
  237.           begin
  238.             inc(PmanVRAM);
  239.             PmanVRAM^ := m1[z];                                                 // четвёртая линия
  240.           end;
  241.  
  242.           inc(y);                               // переходим к следующей линии
  243.           dec(i);                               // смещаем
  244.         end;
  245.         dec(y, 8);                              // вернуть на начальную линию
  246.         dec(x, 8);                              // сдвинуть икс на сделующий спрайт
  247.         dec(i, 8);                              // здесь смещение через спрайт (обработали уже два спрайта).
  248.       end;
  249.       y := y + 8;                               // если 16 спрайтов готово, то перейти на следующие спрайты
  250.     end;
  251.   end;
  252.  
  253. begin
  254.   Result := False;
  255.  
  256.   numData := 0;
  257.  
  258.   //для тестов
  259.   testTimeStart[0] := timer_GetTicks;
  260.   MovData1(data1);
  261.   testTimeEnd[0] := timer_GetTicks - testTimeStart[0];
  262.   testTimeRes[0] := (testTimeRes[0] + testTimeEnd[0]);
  263.  
  264.   testTimeStart[1] := timer_GetTicks;
  265.   MovData2(data1);
  266.   testTimeEnd[1] := timer_GetTicks - testTimeStart[1];
  267.   testTimeRes[1] := (testTimeRes[1] + testTimeEnd[1]);
  268.  
  269.   testTimeStart[2] := timer_GetTicks;
  270.   MovData3(data1);
  271.   testTimeEnd[2] := timer_GetTicks - testTimeStart[2];
  272.   testTimeRes[2] := (testTimeRes[2] + testTimeEnd[2]);
  273.  
  274.  // MovData(data1);
  275.     tex_SetData(ID1, PByteArray(@managerVRAM.dataOut[managerVRAM.Count - 1, numData]), 0, 0, 128, 512);
  276.  
  277.   numData := 1;
  278.  
  279.   testTimeStart[0] := timer_GetTicks;
  280.   MovData1(data2);
  281.   testTimeEnd[0] := timer_GetTicks - testTimeStart[0];
  282.   testTimeRes[0] := (testTimeRes[0] + testTimeEnd[0]);
  283.  
  284.   testTimeStart[1] := timer_GetTicks;
  285.   MovData2(data2);
  286.   testTimeEnd[1] := timer_GetTicks - testTimeStart[1];
  287.   testTimeRes[1] := (testTimeRes[1] + testTimeEnd[1]);
  288.  
  289.   testTimeStart[2] := timer_GetTicks;
  290.   MovData3(data2);
  291.   testTimeEnd[2] := timer_GetTicks - testTimeStart[2];
  292.   testTimeRes[2] := (testTimeRes[2] + testTimeEnd[2]);
  293.  
  294.   // MovData(data2);
  295.     tex_SetData(ID2, PByteArray(@managerVRAM.dataOut[managerVRAM.Count - 1, numData]), 0, 0, 128, 512);
  296.  
  297.   Result := true;
  298. end;
« Last Edit: June 04, 2026, 01:12:35 pm by Seenkao »
Rus: Стремлюсь к созданию минимальных и достаточно быстрых приложений.

Eng: I strive to create applications that are minimal and reasonably fast.
Working on ZenGL

 

TinyPortal © 2005-2018