วัด Memory Bandwidth ของเครื่องคอมพิวเตอร์ด้วย STREAM

ในการสร้างและนำคอมพิวเตอร์มาใช้งาน เราต้องการที่จะรู้ว่าแต่ละเครื่องที่ถูกสร้างขึ้นมีประสิทธิภาพมากน้อยต่างกันแค่ไหนบ้าง ซึ่งโดยทั่วไปเราใช้ benchmark หลายแบบในการวัดประสิทธิภาพของเครื่องในด้านต่างๆ เช่นความสามารถในการคำนวณ ความเร็วของ network เป็นต้น แต่การวัดอีกอันหนึ่งซึ่งมีความสำคัญมากไม่แพ้กันคือการวัด memory bandwidth ของเครื่อง


โดยทั่วไปสิ่งที่เราจะวัดเป็นอันดับแรกคือประสิทธิภาพของการคำนวณ เช่นสามารถประมวลผล floating-point operator ได้เท่าไหร่ในหนึ่งหน่วยเวลา แต่ค่านี้อาจจะยังไม่สามารถบอกได้ทั้งหมดว่าถ้ามี application มาทำงานจริงแล้วจะเร็วแค่ไหน ความเร็วของ cpu เพิ่มขึ้นเร็วมากตาม Moore's law ที่เราคุ้นเคย แต่ว่าความเร็วของ memory เพิ่มขึ้นช้ากว่ามากๆ application จึงอาจถูกจำกัดความเร็วจาก memory แทนได้ ลองนึกถึงเวลาที่ cpu ทำงานได้เร็วมาก แต่โหลดข้อมูลจาก memory ขึ้นมาไม่ทัน สุดท้ายแล้ว cpu ก็จะว่างและใช้งานได้ไม่เต็มประสิทธิภาพ ดังนั้นการรู้ความสามารถของการโหลดข้อมูลจาก memory จึงมีความจำเป็นด้วยสาเหตุนี้


STREAM เป็น benchmark สำหรับวัด bandwidth ในการโหลดข้อมูลขึ้นมาทำงาน สามารถดาวน์โหลดได้จากเว็บไซต์ทางการเลยคือ https://www.cs.virginia.edu/stream/ ซึ่ง benchmark ที่ดาวน์โหลดมาจะใช้ OpenMP อยู่แล้วเราจึงใช้งานกับ cpu แบบหลาย core ได้เลย วิธีการใช้งานก็ง่ายมาก ข้างในตัวที่ดาวน์โหลดมาจะมี 2 เวอร์ชัน คือเวอร์ชันที่เป็น C และ Fortran โดยจะมาในชื่อ stream.c และ stream.f ตามลำดับ ใช้คำสั่ง make จะได้ไฟล์ stream_c.exe และ stream_f.exe มา ใช้ตัวใดตัวหนึ่งรันก็พอครับ ซึ่งในบทความนี้ผมจะใช้แต่ตัวภาษา C นะครับ จะไม่แตะตัว Fortran เลย

 

-------------------------------------------------------------
STREAM version $Revision: 5.10 $
-------------------------------------------------------------
This system uses 8 bytes per array element.
-------------------------------------------------------------
Array size = 10000000 (elements), Offset = 0 (elements)
Memory per array = 76.3 MiB (= 0.1 GiB).
Total memory required = 228.9 MiB (= 0.2 GiB).
Each kernel will be executed 10 times.
 The *best* time for each kernel (excluding the first iteration)
 will be used to compute the reported bandwidth.
-------------------------------------------------------------
Your clock granularity/precision appears to be 1 microseconds.
Each test below will take on the order of 12965 microseconds.
   (= 12965 clock ticks)
Increase the size of the arrays if this shows that
you are not getting at least 20 clock ticks per test.
-------------------------------------------------------------
WARNING -- The above is only a rough guideline.
For best results, please be sure you know the
precision of your system timer.
-------------------------------------------------------------
Function    Best Rate MB/s  Avg time     Min time     Max time
Copy:           11362.8     0.014319     0.014081     0.014382
Scale:          11972.4     0.014016     0.013364     0.014190
Add:            13314.9     0.019570     0.018025     0.019836
Triad:          13830.4     0.019408     0.017353     0.019686
-------------------------------------------------------------
Solution Validates: avg error less than 1.000000e-13 on all three arrays
-------------------------------------------------------------

 

ผมใช้ตัว stream_c.exe รันเลยทันที ผลลัพธ์จะได้ออกมาแบบนี้ บรรทัดแรก จะบอกว่าทั้งหมดนี้เป็นผลลัพธ์ของ STREAM เวอร์ชัน 5.10 ตรง Array size คือบอกจำนวน element ของ array ที่ใช้ ที่ผมรันจะเป็น 10,000,000 และคิดเป็นขนาด 76.3 MB ในส่วนด้านล่างที่ดูเป็นตาราง ตัวเลขที่สำคัญคือตรง Best Rate MB/s ส่วนนี้จะบอก bandwidth ที่ได้จากการรัน ซึ่งจะมีทั้งหมด 4 ค่า มาจาก kernel ทั้ง 4 ตัว STREAM นั่นเอง ได้แก่ Copy, Scale, Add และ Triad โค้ดของ STREAM นั่นง่ายมากครับ kernel ทั้ง 4 ตัวที่กล่าวมาทำงานแบบนี้

Name Kernel
Copy a[i] = b[i]
Scale a[i] = q * b[i]
Add a[i] = b[i] + c[i]
Triad a[i] = b[i] + q * c[i]

ซึ่ง Copy จะทำแค่ copy ข้อมูลจาก array b ไป a ปกติแล้วตัวนี้จะให้ค่าดีสุดครับ เพราะมันทำอะไรน้อยสุด

 

หลังจากนี้เราจะมาค่อยๆ ลองรีดประสิทธิภาพที่แท้จริงของเครื่องกันนะครับว่าเป็นเท่าไหร่กันแน่ ก่อนอื่นที่ต้องรู้ก่อนก็คือกฎการรัน STREAM คือ
 

ขนาดของ array แต่ละตัวต้องใหญ่กว่าอย่างน้อย 4 เท่าของขนาดของ Cache ที่ใหญ่ที่สุดรวมกัน 


Cache ที่ใหญ่ที่สุดก็คือ cache level ล่างสุดของ cpu เรานั่นเอง เช่นในที่นี้ผมใช้ Intel® Xeon® Processor E5-2620 v3 ซึ่ง cache ใหญ่สุดของมันคือ 15MB (อ้างอิงจากที่นี่) ดังนั้นขนาดอาร์เรย์แต่ละตัวของผมควรจะเกิน 60MB ซึ่งตอนนี้มันเป็น 76.3MB ก็ถือว่าผ่าน กรณีเครื่องผมมี cpu เดียวแบบนี้ถือว่าโอเคแล้ว แต่ถ้าเครื่องมีหลาย cpu จะต้องเอาขนาด cache สูงสุดของทุกตัวมารวมกันก่อนนะครับ แต่ถ้ามีรุ่นเดียวกันนี้ 4 ตัว แปลว่า cache เป็น 60MB ดังนั้น array แต่ละตัวของผมควรจะใหญ่กว่า 240MB ครับ


ถ้า array ของเราเล็กเกินไป จะกำหนดขนาดใหม่ก็ไม่ยากครับ ในตอน compile ให้ใส่ option เข้าไปว่า -DSTREAM_ARRAY_SIZE=40000000 แบบนี้ก็จะได้ array จำนวน 40000000 elements แต่เวลาคิดต้องคิดเป็น bytes ครับ ก็เอาขนาดของ floating-point ที่ใช้คูณเข้าไป


cpu ของ Intel สามารถดู memory bandwidth สูงสุดตามทฤษฎี ได้จากเว็บของ Intel เอง เช่นของ cpu ที่ผมใช้ก็ดูจากเว็บนี้ได้เลย ซึ่งในนั้นจะบอกว่ารุ่นที่ผมใช้อยู่มี bandwidth สูงสุดคือ 59GB/s แต่ถ้าลองดูจาก benchmark ที่รันแล้ว kernel ที่ดีที่สุดยังได้แค่ 13.83GB/s เอง ที่เป็นแบบนี้เพราะ cpu ยังคำนวณได้ไม่เร็วพอ cpu มีถึง 6 cores แต่ว่าเราใช้แค่ cores เดียว ซึ่งเราจะใช้ OpenMP (ที่ซึ่งเขียนมาใน benchmark ให้เราอยู่แล้ว) ทำให้มันเร็วขึ้น ก่อนอื่นก็เพิ่ม flags -fopenmp ลงไปใน Makefile ลอง make ใหม่ และรันอีกครั้ง แต่ก่อนรันลองกำหนดจำนวน threads ให้เท่ากับจำนวน cores ซึ่งก็คือ 6 ก่อน ด้วยคำสั่ง export OMP_NUM_THREADS=6 แล้วก็รันเหมือนเดิม จะได้ผลลัพธ์ออกมาแบบนี้

 

-------------------------------------------------------------
Number of Threads requested = 6
Number of Threads counted = 6
-------------------------------------------------------------
...
-------------------------------------------------------------
Function    Best Rate MB/s  Avg time     Min time     Max time
Copy:           28808.3     0.005596     0.005554     0.005755
Scale:          27846.0     0.005781     0.005746     0.005881
Add:            30927.6     0.007819     0.007760     0.008004
Triad:          31152.6     0.007765     0.007704     0.007953
-------------------------------------------------------------

 

ในผลลัพธ์ใหม่ที่ได้ จะมีข้อความเพิ่มขึ้นมาบอกว่า เรารันด้วย threads เท่ากับ 6 และจะเห็นว่าทุก kernel มี bandwidth เพิ่มขึ้นจากเดิม แต่ยังห่างจาก 59GB/s อยู่พอสมควร โดย default แล้ว optimization flags ที่กำหนดมาใน Makefile จะเป็น -O2 ผมจะลองเปลี่ยนเป็น -O3 แล้วรันใหม่ดู

 

-------------------------------------------------------------
Function    Best Rate MB/s  Avg time     Min time     Max time
Copy:           40701.6     0.003970     0.003931     0.004059
Scale:          28622.7     0.005628     0.005590     0.005751
Add:            30584.7     0.007910     0.007847     0.008076
Triad:          31027.7     0.007783     0.007735     0.007928
-------------------------------------------------------------

 

ผลลัพธ์ที่ได้ก็จะเร็วขึ้นไปอีก อาจจะลองเล่นด้วยการปรับหา compiler flags ต่างๆ ที่เกี่ยวข้องดูครับ ว่าทำยังไงให้เร็วขึ้นได้อีกบ้าง แต่ผมจะลองเปลี่ยน compiler จาก gcc ไป icc ของ Intel ดูแล้วลองใช้ flags ง่ายๆ ต่อไปนี้ (ใน Makefile ของ STREAM มีตัวอย่างให้ครับ)

  • -O3   เปิด optimization level 3
  • -qopenmp   ใช้ OpenMP
  • -xcore-avx2   ใช้ชุดคำสั่ง AVX2

 

-------------------------------------------------------------
Function    Best Rate MB/s  Avg time     Min time     Max time
Copy:           42801.8     0.003780     0.003738     0.003872
Scale:          43276.5     0.003735     0.003697     0.003791
Add:            43368.8     0.005599     0.005534     0.005811
Triad:          43415.6     0.005589     0.005528     0.005784
-------------------------------------------------------------

 

จะเห็นว่าเร็วขึ้นมากทีเดียว ผมลองรันแบบกำหนดจำนวน threads ต่างๆดู เนื่องจาก cpu มี 6 cores และมี hyper-threadingด้วย ผมเลยลองตั้งแต่ 1 - 12 threads ซึ่งได้ประสิทธิภาพมาตามนี้

 

    

 

bandwidth จะขึ้นมา peak ที่ 5-6 threads และหลังจากนั้นก็จะเริ่มคงที่แล้ว ซึ่งแสดงว่ากำหนดจำนวน threads ให้เท่ากับ cores จริงๆ ไปเลยดีกว่า hyper-threading ไม่ช่วยอะไร


กรณีที่เครื่องที่เราต้องการวัดเป็นเครื่อง cluster ที่มีหลายเครื่องต่อกัน STREAM ก็มีเวอร์ชัน MPI ด้วยครับ หาได้จากเว็บหลักเลย แต่ต้องคุ้ยๆหน่อย คนทำบอกว่าไม่ชอบ MPI และไม่อยากเอาขึ้นเป็นตัว standard สำหรับการเทส STREAM บน cluster จำเป็นจะต้อง tune ตัว network ด้วยนะครับ เพื่อให้ bandwidth รวมไม่ตกลงเยอะ

 

Reference

  1. https://www.cs.virginia.edu/stream/
  2. McCalpin, John D., 1995: "Memory Bandwidth and Machine Balance in Current High Performance Computers", IEEE Computer Society Technical Committee on Computer Architecture (TCCA) Newsletter, December 1995.

Taxonomy upgrade extras: