Highway เป็นไลบรารี C++ ที่ให้บริการ SIMD/เวกเตอร์ภายในแบบพกพา
เอกสารประกอบ
ก่อนหน้านี้ได้รับใบอนุญาตภายใต้ Apache 2 ปัจจุบันได้รับใบอนุญาตแบบคู่เป็น Apache 2 / BSD-3
เรามีความหลงใหลในซอฟต์แวร์ประสิทธิภาพสูง เราเห็นศักยภาพที่สำคัญที่ยังไม่ได้ใช้ใน CPU (เซิร์ฟเวอร์ อุปกรณ์พกพา เดสก์ท็อป) Highway เหมาะสำหรับวิศวกรที่ต้องการก้าวข้ามขีดจำกัดที่เป็นไปได้ในซอฟต์แวร์อย่างน่าเชื่อถือและประหยัด
CPU ให้คำแนะนำ SIMD/เวกเตอร์ที่ใช้การดำเนินการเดียวกันกับรายการข้อมูลหลายรายการ ซึ่งสามารถลดการใช้พลังงานได้ เช่น ห้าเท่า เนื่องจากมีการดำเนินการตามคำสั่งน้อยลง เรามักจะเห็นการเร่งความเร็ว 5-10 เท่า
Highway ทำให้การเขียนโปรแกรม SIMD/เวกเตอร์ใช้งานได้จริงและสามารถทำงานได้ตามหลักการชี้นำเหล่านี้:
ทำในสิ่งที่คุณคาดหวัง : Highway คือไลบรารี C++ ที่มีฟังก์ชันที่เลือกสรรมาอย่างดี ซึ่งแมปกับคำสั่ง CPU ได้ดี โดยไม่ต้องแปลงคอมไพเลอร์อย่างกว้างขวาง โค้ดที่ได้นั้นสามารถคาดเดาได้และมีประสิทธิภาพในการเปลี่ยนแปลงโค้ด/อัพเดตคอมไพเลอร์มากกว่าการทำให้เวกเตอร์อัตโนมัติ
ทำงานบนแพลตฟอร์มที่ใช้กันอย่างแพร่หลาย : Highway รองรับสถาปัตยกรรมห้าแบบ รหัสแอปพลิเคชันเดียวกันสามารถกำหนดเป้าหมายชุดคำสั่งต่างๆ รวมถึงชุดคำสั่งที่มีเวกเตอร์ 'ปรับขนาดได้' (ไม่ทราบขนาด ณ เวลารวบรวม) Highway ต้องการเพียง C++11 และรองรับคอมไพเลอร์สี่ตระกูล หากคุณต้องการใช้ Highway บนแพลตฟอร์มอื่น โปรดแจ้งปัญหา
ปรับใช้ได้อย่างยืดหยุ่น : แอปพลิเคชันที่ใช้ Highway สามารถทำงานบนคลาวด์หรืออุปกรณ์ไคลเอ็นต์ที่ต่างกัน โดยเลือกชุดคำสั่งที่ดีที่สุดในรันไทม์ อีกทางหนึ่ง นักพัฒนาอาจเลือกที่จะกำหนดเป้าหมายชุดคำสั่งเดียวโดยไม่มีค่าใช้จ่ายรันไทม์ใดๆ ในทั้งสองกรณี รหัสแอปพลิเคชันจะเหมือนกัน ยกเว้นการสลับ HWY_STATIC_DISPATCH
ด้วย HWY_DYNAMIC_DISPATCH
บวกด้วยโค้ดหนึ่งบรรทัด ดูเพิ่มเติมที่ คำแนะนำเกี่ยวกับการจัดส่งของ @kfjahnke
เหมาะสำหรับโดเมนที่หลากหลาย : Highway มีชุดการดำเนินการที่ครอบคลุม ใช้สำหรับการประมวลผลภาพ (จุดลอยตัว) การบีบอัด การวิเคราะห์วิดีโอ พีชคณิตเชิงเส้น การเข้ารหัส การเรียงลำดับ และการสร้างแบบสุ่ม เราทราบดีว่ากรณีการใช้งานใหม่อาจต้องมีการดำเนินการเพิ่มเติม และยินดีที่จะเพิ่มในส่วนที่เหมาะสม (เช่น ไม่มีประสิทธิภาพที่เพิ่มสูงขึ้นในสถาปัตยกรรมบางตัว) หากคุณต้องการหารือกรุณายื่นประเด็น
การออกแบบข้อมูลแบบขนานของรางวัล : Highway มีเครื่องมือต่างๆ เช่น Gather, MaskedLoad และ FixTag เพื่อเปิดใช้งานการเร่งความเร็วสำหรับโครงสร้างข้อมูลแบบเดิม อย่างไรก็ตาม ประโยชน์สูงสุดจะปลดล็อกได้ด้วยการออกแบบอัลกอริธึมและโครงสร้างข้อมูลสำหรับเวกเตอร์ที่ปรับขนาดได้ เทคนิคที่เป็นประโยชน์ ได้แก่ การจัดแบทช์ เค้าโครงโครงสร้างของอาเรย์ และการจัดสรรแบบจัดเรียง/แบบบุนวม
เราขอแนะนำแหล่งข้อมูลเหล่านี้สำหรับการเริ่มต้น:
การสาธิตออนไลน์โดยใช้ Compiler Explorer:
เราสังเกตเห็นว่ามีการอ้างอิง Highway ในโครงการโอเพ่นซอร์สต่อไปนี้ ซึ่งพบได้ทาง sourcegraph.com ส่วนใหญ่เป็นที่เก็บ GitHub หากคุณต้องการเพิ่มโครงการของคุณหรือลิงก์ไปยังโครงการโดยตรง โปรดแจ้งปัญหาหรือติดต่อเราผ่านทางอีเมลด้านล่างนี้
อื่น
หากคุณต้องการรับ Highway นอกเหนือจากการโคลนจากพื้นที่เก็บข้อมูล GitHub นี้หรือใช้เป็นโมดูลย่อย Git คุณยังสามารถค้นหาได้ในตัวจัดการแพ็คเกจหรือพื้นที่เก็บข้อมูลต่อไปนี้:
ดูรายการได้ที่https://repology.org/project/highway-simd-library/versions
ทางหลวงรองรับ 24 เป้าหมาย ตามลำดับตัวอักษรของแพลตฟอร์ม:
EMU128
, SCALAR
;NEON_WITHOUT_AES
, NEON
, NEON_BF16
, SVE
, SVE2
, SVE_256
, SVE2_128
;Z14
, Z15
;PPC8
(v2.07), PPC9
(v3.0), PPC10
(v3.1B, ยังไม่รองรับเนื่องจากข้อผิดพลาดของคอมไพเลอร์, ดู #1207; ต้องใช้ QEMU 7.2 ด้วย);RVV
(1.0);WASM
, WASM_EMU256
(wasm128 เวอร์ชันที่ไม่ได้ม้วนออก 2 เท่า เปิดใช้งานหากมีการกำหนด HWY_WANT_WASM2
ซึ่งจะยังคงได้รับการสนับสนุนจนกว่า WASM เวอร์ชันในอนาคตจะเข้ามาแทนที่);SSE2
SSSE3
(~อินเทลคอร์)SSE4
(~เนเฮเลม รวมถึง AES + CLMUL ด้วย)AVX2
(~Haswell รวมถึง BMI2 + F16 + FMA ด้วย)AVX3
(~สกายเลค, AVX-512F/BW/ซีดี/DQ/VL)AVX3_DL
(~Icelake รวมถึง BitAlg + CLMUL + GFNI + VAES + VBMI + VBMI2 + VNNI + VPOPCNT; ต้องเลือกใช้โดยกำหนด HWY_WANT_AVX3_DL
เว้นแต่จะคอมไพล์สำหรับการจัดส่งแบบคงที่)AVX3_ZEN4
(เช่น AVX3_DL แต่ปรับให้เหมาะสมสำหรับ AMD Zen4; ต้องเลือกใช้โดยการกำหนด HWY_WANT_AVX3_ZEN4
หากคอมไพล์สำหรับการจัดส่งแบบคงที่ แต่เปิดใช้งานตามค่าเริ่มต้นสำหรับการจัดส่งรันไทม์)AVX3_SPR
(~แซฟไฟร์ ราปิดส์ รวม AVX-512FP16)นโยบายของเราคือ เว้นแต่จะระบุไว้เป็นอย่างอื่น เป้าหมายจะยังคงได้รับการสนับสนุนตราบใดที่สามารถคอมไพล์ (ข้าม) ด้วย Clang หรือ GCC ที่รองรับในปัจจุบัน และทดสอบโดยใช้ QEMU หากเป้าหมายสามารถคอมไพล์ด้วย LLVM trunk และทดสอบโดยใช้ QEMU เวอร์ชันของเราโดยไม่ต้องมีแฟล็กเพิ่มเติม ก็แสดงว่าเป้าหมายนั้นมีสิทธิ์รวมไว้ในโครงสร้างพื้นฐานการทดสอบต่อเนื่องของเรา มิฉะนั้น เป้าหมายจะถูกทดสอบด้วยตนเองก่อนเผยแพร่ด้วยเวอร์ชัน/การกำหนดค่าที่เลือกของ Clang และ GCC
SVE ได้รับการทดสอบครั้งแรกโดยใช้ farm_sve (ดูการตอบรับ)
การเปิดตัว Highway มีเป้าหมายที่จะเป็นไปตามระบบ semver.org (MAJOR.MINOR.PATCH) โดยเพิ่มค่า MINOR หลังจากการเพิ่มเติมที่เข้ากันได้แบบย้อนหลัง และ PATCH หลังจากการแก้ไขที่เข้ากันได้แบบย้อนหลัง เราขอแนะนำให้ใช้รีลีส (แทนที่จะใช้ทิป Git) เนื่องจากมีการทดสอบอย่างกว้างขวางกว่า ดูด้านล่าง
เวอร์ชันปัจจุบัน 1.0 ส่งสัญญาณการมุ่งเน้นที่เพิ่มมากขึ้นในเรื่องความเข้ากันได้แบบย้อนหลัง แอปพลิเคชันที่ใช้ฟังก์ชันการทำงานที่บันทึกไว้จะยังคงเข้ากันได้กับการอัปเดตในอนาคตที่มีหมายเลขเวอร์ชันหลักเดียวกัน
การทดสอบการรวมอย่างต่อเนื่องสร้างขึ้นด้วย Clang เวอร์ชันล่าสุด (ทำงานบน x86 แบบเนทีฟ หรือ QEMU สำหรับ RISC-V และ Arm) และ MSVC 2019 (v19.28 ทำงานบน x86 แบบเนทีฟ)
ก่อนการเผยแพร่ เรายังทดสอบบน x86 ด้วย Clang และ GCC และ Armv7/8 ผ่านการคอมไพล์ข้าม GCC ดูขั้นตอนการทดสอบสำหรับรายละเอียด
ไดเร็กทอรี contrib
มียูทิลิตี้ที่เกี่ยวข้องกับ SIMD: คลาสรูปภาพที่มีแถวเรียงกัน, ไลบรารีคณิตศาสตร์ (มีการใช้งาน 16 ฟังก์ชันแล้ว ซึ่งส่วนใหญ่เป็นตรีโกณมิติ) และฟังก์ชันสำหรับการคำนวณดอทโปรดัคและการเรียงลำดับ
หากคุณต้องการเพียงการสนับสนุน x86 คุณสามารถใช้ไลบรารีคลาสเวกเตอร์ VCL ของ Agner Fog ได้ มันมีฟังก์ชั่นมากมายรวมถึงห้องสมุดคณิตศาสตร์ที่สมบูรณ์
หากคุณมีโค้ดที่มีอยู่โดยใช้ x86/NEON intrinsics คุณอาจสนใจ SIMDe ซึ่งจำลอง intrinsics เหล่านั้นโดยใช้ intrinsics ของแพลตฟอร์มอื่นหรือ autovectorization
โปรเจ็กต์นี้ใช้ CMake เพื่อสร้างและสร้าง ในระบบที่ใช้ Debian คุณสามารถติดตั้งได้ผ่าน:
sudo apt install cmake
การทดสอบหน่วยของทางหลวงใช้ googletest ตามค่าเริ่มต้น CMake ของ Highway จะดาวน์โหลดการขึ้นต่อกันนี้ ณ เวลากำหนดค่า คุณสามารถหลีกเลี่ยงปัญหานี้ได้โดยตั้งค่าตัวแปร HWY_SYSTEM_GTEST
CMake เป็น ON และติดตั้ง gtest แยกต่างหาก:
sudo apt install libgtest-dev
หรือคุณสามารถกำหนด HWY_TEST_STANDALONE=1
และลบ gtest_main
ที่เกิดขึ้นทั้งหมดในไฟล์ BUILD แต่ละไฟล์ จากนั้นการทดสอบจะหลีกเลี่ยงการพึ่งพา GUnit
การรันการทดสอบแบบคอมไพล์ข้ามต้องได้รับการสนับสนุนจากระบบปฏิบัติการ ซึ่งบน Debian จัดทำโดยแพ็คเกจ qemu-user-binfmt
หากต้องการสร้าง Highway เป็นไลบรารีที่ใช้ร่วมกันหรือแบบคงที่ (ขึ้นอยู่กับ BUILD_SHARED_LIBS) คุณสามารถใช้เวิร์กโฟลว์ CMake มาตรฐานได้:
mkdir -p build && cd build
cmake ..
make -j && make test
หรือคุณสามารถเรียกใช้ run_tests.sh
( run_tests.bat
บน Windows)
Bazel ยังรองรับการสร้างด้วย แต่ไม่ได้ใช้/ทดสอบกันอย่างแพร่หลาย
เมื่อสร้าง Armv7 ข้อจำกัดของคอมไพเลอร์ปัจจุบันกำหนดให้คุณต้องเพิ่ม -DHWY_CMAKE_ARM7:BOOL=ON
ลงในบรรทัดคำสั่ง CMake ดู #834 และ #1032 เราทราบดีว่ากำลังดำเนินการเพื่อลบข้อจำกัดนี้
ไม่รองรับการสร้างบน x86 แบบ 32 บิตอย่างเป็นทางการ และ AVX2/3 จะถูกปิดใช้งานตามค่าเริ่มต้นที่นั่น โปรดทราบว่า johnplatts ได้สร้างและรันการทดสอบ Highway บน 32-บิต x86 สำเร็จ รวมถึง AVX2/3 บน GCC 7/8 และ Clang 8/11/12 บน Ubuntu 22.04, Clang 11 และ 12 แต่ไม่ใช่เวอร์ชันที่ใหม่กว่า จำเป็นต้องมีแฟล็กคอมไพเลอร์เพิ่มเติม -m32 -isystem /usr/i686-linux-gnu/include
Clang 10 และรุ่นก่อนหน้าจำเป็นต้องมี plus -isystem /usr/i686-linux-gnu/include/c++/12/i686-linux-gnu
ข้างต้น ดู #1279
Highway มีให้บริการแล้วใน vcpkg
vcpkg install highway
พอร์ตทางหลวงใน vcpkg ได้รับการปรับปรุงให้ทันสมัยโดยสมาชิกทีม Microsoft และผู้ร่วมให้ข้อมูลในชุมชน หากเวอร์ชันล้าสมัย โปรดสร้างปัญหาหรือดึงคำขอบนที่เก็บ vcpkg
คุณสามารถใช้ benchmark
ภายในตัวอย่าง/ เป็นจุดเริ่มต้นได้
หน้าอ้างอิงด่วนแสดงรายการการดำเนินการทั้งหมดและพารามิเตอร์โดยย่อ และ Instruction_matrix ระบุจำนวนคำสั่งต่อการดำเนินการ
คำถามที่พบบ่อยจะตอบคำถามเกี่ยวกับการพกพา การออกแบบ API และตำแหน่งที่จะค้นหาข้อมูลเพิ่มเติม
เราขอแนะนำให้ใช้เวกเตอร์ SIMD เต็มรูปแบบทุกครั้งที่เป็นไปได้เพื่อการพกพาที่มีประสิทธิภาพสูงสุด หากต้องการขอรับ ให้ส่งแท็ก ScalableTag
(หรือเทียบเท่า HWY_FULL(float)
) ไปยังฟังก์ชันต่างๆ เช่น Zero/Set/Load
มีสองทางเลือกสำหรับกรณีการใช้งานที่ต้องมีขอบเขตบนบนเลน:
สำหรับเลนสูงสุด N
ให้ระบุ CappedTag
หรือเทียบเท่า HWY_CAPPED(T, N)
จำนวนเลนจริงจะ N
ปัดเศษลงด้วยกำลังที่ใกล้ที่สุดของ 2 เช่น 4 หาก N
คือ 5 หรือ 8 หาก N
คือ 8 สิ่งนี้มีประโยชน์สำหรับโครงสร้างข้อมูล เช่น เมทริกซ์แคบ ยังคงต้องมีการวนซ้ำเนื่องจากเวกเตอร์อาจมีเลนน้อยกว่า N
สำหรับกำลังของสองเลน N
ให้ระบุ FixedTag
N
ที่รองรับที่ใหญ่ที่สุดขึ้นอยู่กับเป้าหมาย แต่รับประกันว่าจะมีอย่างน้อย 16/sizeof(T)
เนื่องจากข้อจำกัดของ ADL รหัสผู้ใช้ที่เรียก Highway ops จะต้อง:
namespace hwy { namespace HWY_NAMESPACE {
; หรือnamespace hn = hwy::HWY_NAMESPACE; hn::Add()
; หรือusing hwy::HWY_NAMESPACE::Add;
- นอกจากนี้ แต่ละฟังก์ชันที่เรียก Highway ops (เช่น Load
) จะต้องขึ้นต้นด้วย HWY_ATTR
หรืออยู่ระหว่าง HWY_BEFORE_NAMESPACE()
และ HWY_AFTER_NAMESPACE()
ขณะนี้ฟังก์ชัน Lambda ต้องใช้ HWY_ATTR
ก่อนเครื่องหมายปีกกาเปิด
อย่าใช้เนมสเปซขอบเขตหรือตัวเริ่มต้นแบบ static
ที่สำหรับเวกเตอร์ SIMD เนื่องจากอาจทำให้เกิด SIGILL เมื่อใช้การจัดส่งแบบรันไทม์ และคอมไพเลอร์เลือกตัวเริ่มต้นที่คอมไพล์สำหรับเป้าหมายที่ไม่รองรับโดย CPU ปัจจุบัน โดยทั่วไปค่าคงที่ที่เริ่มต้นผ่าน Set
ควรเป็นตัวแปรท้องถิ่น (const)
จุดเข้าสู่รหัสโดยใช้ Highway แตกต่างกันเล็กน้อยขึ้นอยู่กับว่าพวกเขาใช้การจัดส่งแบบคงที่หรือแบบไดนามิก ในทั้งสองกรณี เราขอแนะนำให้ฟังก์ชันระดับบนสุดได้รับตัวชี้หนึ่งตัวหรือมากกว่าไปยังอาร์เรย์ แทนที่จะเป็นประเภทเวกเตอร์เฉพาะเป้าหมาย
สำหรับการจัดส่งแบบคงที่ HWY_TARGET
จะเป็นเป้าหมายที่ดีที่สุดในบรรดา HWY_BASELINE_TARGETS
เช่นเป้าหมายที่คอมไพเลอร์อนุญาตให้ใช้ (ดูข้อมูลอ้างอิงด่วน) สามารถเรียกใช้ฟังก์ชันภายใน HWY_NAMESPACE
ได้โดยใช้ HWY_STATIC_DISPATCH(func)(args)
ภายในโมดูลเดียวกันกับที่ฟังก์ชันเหล่านั้นกำหนดไว้ คุณสามารถเรียกใช้ฟังก์ชันจากโมดูลอื่นได้โดยการล้อมไว้ในฟังก์ชันปกติและประกาศฟังก์ชันปกติในส่วนหัว
สำหรับการจัดส่งแบบไดนามิก ตารางของตัวชี้ฟังก์ชันจะถูกสร้างขึ้นผ่านมาโคร HWY_EXPORT
ที่ใช้โดย HWY_DYNAMIC_DISPATCH(func)(args)
เพื่อเรียกตัวชี้ฟังก์ชันที่ดีที่สุดสำหรับเป้าหมายที่ CPU ปัจจุบันรองรับ โมดูลจะถูกคอมไพล์โดยอัตโนมัติสำหรับแต่ละเป้าหมายใน HWY_TARGETS
(ดูข้อมูลอ้างอิงด่วน) หากมีการกำหนด HWY_TARGET_INCLUDE
และ foreach_target.h
รวมอยู่ด้วย โปรดทราบว่าการเรียกใช้ครั้งแรกของ HWY_DYNAMIC_DISPATCH
หรือการเรียกแต่ละครั้งไปยังตัวชี้ที่ส่งคืนโดยการเรียกใช้ครั้งแรกของ HWY_DYNAMIC_POINTER
เกี่ยวข้องกับโอเวอร์เฮดการตรวจจับ CPU บางส่วน คุณสามารถป้องกันสิ่งนี้ได้ด้วยการเรียกสิ่งต่อไปนี้ก่อนการเรียกใช้ HWY_DYNAMIC_*
: hwy::GetChosenTarget().Update(hwy::SupportedTargets());
-
ดูคำแนะนำแยกต่างหากเกี่ยวกับการจัดส่งแบบไดนามิกโดย @kfjahnke
เมื่อใช้การจัดส่งแบบไดนามิก foreach_target.h
จะถูกรวมจากหน่วยการแปล (ไฟล์ .cc) ไม่ใช่ส่วนหัว ส่วนหัวที่มีโค้ดเวกเตอร์ที่ใช้ร่วมกันระหว่างหน่วยการแปลหลายหน่วยจำเป็นต้องมีการป้องกันพิเศษ เช่น ตัวอย่างต่อไปนี้นำมาจาก examples/skeleton-inl.h
:
#if defined(HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_) == defined(HWY_TARGET_TOGGLE)
#ifdef HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_
#undef HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_
#else
#define HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_
#endif
#include "hwy/highway.h"
// Your vector code
#endif
ตามแบบแผน เราตั้งชื่อส่วนหัวดังกล่าว -inl.h
เนื่องจากเนื้อหา (มักเป็นเทมเพลตฟังก์ชัน) มักจะอยู่ในบรรทัด
แอปพลิเคชันควรได้รับการรวบรวมโดยเปิดใช้งานการปรับให้เหมาะสมที่สุด หากไม่มีโค้ด SIMD ในบรรทัดอาจทำให้ช้าลง 10 ถึง 100 เท่า สำหรับเสียงดังกราวและ GCC โดยทั่วไปแล้ว -O2
ก็เพียงพอแล้ว
สำหรับ MSVC เราขอแนะนำให้คอมไพล์ด้วย /Gv
เพื่ออนุญาตให้ฟังก์ชันที่ไม่อยู่ในบรรทัดส่งผ่านอาร์กิวเมนต์เวกเตอร์ในรีจิสเตอร์ หากต้องการใช้เป้าหมาย AVX2 ร่วมกับเวกเตอร์แบบครึ่งความกว้าง (เช่น PromoteTo
) สิ่งสำคัญคือต้องคอมไพล์ด้วย /arch:AVX2
นี่ดูเหมือนจะเป็นวิธีเดียวที่จะสร้างคำสั่ง SSE ที่เข้ารหัส VEX บน MSVC ได้อย่างน่าเชื่อถือ บางครั้ง MSVC จะสร้างคำสั่ง SSE ที่เข้ารหัส VEX หากผสมกับ AVX แต่ไม่เสมอไป โปรดดู DevCom-10618264 มิฉะนั้น การผสมคำสั่ง AVX2 ที่เข้ารหัส VEX และที่ไม่ใช่ VEX SSE อาจทำให้ประสิทธิภาพลดลงอย่างรุนแรง น่าเสียดาย ด้วยตัวเลือก /arch:AVX2
ไบนารีที่ได้ผลลัพธ์จะต้องใช้ AVX2 โปรดทราบว่าไม่จำเป็นต้องใช้แฟล็กดังกล่าวสำหรับ clang และ GCC เนื่องจากรองรับแอตทริบิวต์เฉพาะเป้าหมาย ซึ่งเราใช้เพื่อให้แน่ใจว่าการสร้างโค้ด VEX ที่เหมาะสมสำหรับเป้าหมาย AVX2
เมื่อทำการวนซ้ำเวกเตอร์ คำถามสำคัญก็คือว่าจะจัดการกับการวนซ้ำจำนวนหนึ่งหรือไม่และอย่างไร ('การนับการเดินทาง' count
ถึงการนับ ) ที่ไม่แบ่งขนาดเวกเตอร์เท่ากัน N = Lanes(d)
ตัวอย่างเช่น อาจจำเป็นต้องหลีกเลี่ยงการเขียนเลยจุดสิ้นสุดของอาร์เรย์
ในส่วนนี้ ให้ T
แสดงถึงประเภทองค์ประกอบและ d = ScalableTag
สมมติว่าเนื้อความของลูปถูกกำหนดให้เป็นฟังก์ชัน template
"Strip-mining" เป็นเทคนิคสำหรับการเวคเตอร์ของลูปโดยการแปลงให้เป็นลูปด้านนอกและลูปด้านใน เพื่อให้จำนวนการวนซ้ำในลูปด้านในตรงกับความกว้างของเวกเตอร์ จากนั้นวงในจะถูกแทนที่ด้วยการดำเนินการแบบเวกเตอร์
Highway เสนอกลยุทธ์หลายประการสำหรับการทำเวกเตอร์แบบวนซ้ำ:
ตรวจสอบให้แน่ใจว่าอินพุต/เอาต์พุตทั้งหมดมีเบาะ จากนั้นลูป (ด้านนอก) ก็เป็นเพียง
for (size_t i = 0; i < count; i += N) LoopBody(d, i, 0);
ที่นี่ ไม่จำเป็นต้องมีพารามิเตอร์เทมเพลตและอาร์กิวเมนต์ฟังก์ชันที่สอง
นี่เป็นตัวเลือกที่ต้องการ เว้นแต่ว่า N
จะอยู่ในหลักพันและการดำเนินการเวกเตอร์จะถูกส่งไปพร้อมกับเวลาแฝงที่ยาว นี่เป็นกรณีของซูเปอร์คอมพิวเตอร์ในยุค 90 แต่ปัจจุบัน ALU มีราคาถูก และเราเห็นว่าการใช้งานส่วนใหญ่แบ่งเวกเตอร์ออกเป็น 1, 2 หรือ 4 ส่วน ดังนั้นจึงมีค่าใช้จ่ายเพียงเล็กน้อยในการประมวลผลเวกเตอร์ทั้งหมด แม้ว่าเราจะไม่ต้องการเลนทั้งหมดก็ตาม อันที่จริงสิ่งนี้จะช่วยหลีกเลี่ยงต้นทุน (อาจมีขนาดใหญ่) ของการภาคแสดงหรือการโหลด/จัดเก็บบางส่วนบนเป้าหมายเก่า และไม่ทำซ้ำโค้ด
ประมวลผลเวกเตอร์ทั้งหมดและรวมองค์ประกอบที่ประมวลผลก่อนหน้านี้ไว้ในเวกเตอร์สุดท้าย:
for (size_t i = 0; i < count; i += N) LoopBody(d, HWY_MIN(i, count - N), 0);
นี่คือตัวเลือกที่ต้องการอันดับสอง โดยมีเงื่อนไขว่า count >= N
และ LoopBody
นั้นเป็น idempotent องค์ประกอบบางอย่างอาจถูกประมวลผลสองครั้ง แต่โดยปกติแล้วเส้นทางโค้ดเดียวและการทำเวกเตอร์แบบเต็มก็คุ้มค่า แม้ว่า count < N
ก็มักจะเหมาะสมที่จะแพดอินพุต/เอาท์พุตสูงสุด N
ใช้ฟังก์ชัน Transform*
ใน hwy/contrib/algo/transform-inl.h สิ่งนี้จะดูแลการจัดการลูปและส่วนที่เหลือ และคุณเพียงกำหนดฟังก์ชันแลมบ์ดาทั่วไป (C++14) หรือฟังก์ชันที่รับเวกเตอร์ปัจจุบันจากอาร์เรย์อินพุต/เอาต์พุต บวกกับเวกเตอร์ทางเลือกจากอาร์เรย์อินพุตพิเศษสูงสุดสองตัว แล้วส่งคืน ค่าที่จะเขียนไปยังอาร์เรย์อินพุต/เอาต์พุต
นี่คือตัวอย่างการใช้ฟังก์ชัน BLAS SAXPY ( alpha * x + y
):
Transform1(d, x, n, y, [](auto d, const auto v, const auto v1) HWY_ATTR {
return MulAdd(Set(d, alpha), v, v1);
});
ประมวลผลเวกเตอร์ทั้งหมดตามข้างต้น ตามด้วยสเกลาร์ลูป:
size_t i = 0;
for (; i + N <= count; i += N) LoopBody(d, i, 0);
for (; i < count; ++i) LoopBody(CappedTag(), i, 0);
ไม่จำเป็นต้องใช้พารามิเตอร์เทมเพลตและอาร์กิวเมนต์ฟังก์ชันที่สองอีกต่อไป
วิธีนี้จะช่วยหลีกเลี่ยงรหัสที่ซ้ำกัน และสมเหตุสมผลหากมี count
จำนวนมาก หาก count
น้อย การวนรอบที่สองอาจช้ากว่าตัวเลือกถัดไป
ประมวลผลเวกเตอร์ทั้งหมดดังที่กล่าวข้างต้น ตามด้วยการเรียกครั้งเดียวไปยัง LoopBody
ที่ได้รับการแก้ไขด้วยการมาสก์:
size_t i = 0;
for (; i + N <= count; i += N) {
LoopBody(d, i, 0);
}
if (i < count) {
LoopBody(d, i, count - i);
}
ตอนนี้พารามิเตอร์เทมเพลตและอาร์กิวเมนต์ฟังก์ชันที่สามสามารถใช้ภายใน LoopBody
เพื่อ 'ผสมผสาน' เลน num_remaining
แรกของ v
ที่ไม่ใช่แบบอะตอมกับเนื้อหาก่อนหน้าของหน่วยความจำในตำแหน่งต่อมา: BlendedStore(v, FirstN(d, num_remaining), d, pointer);
- ในทำนองเดียวกัน MaskedLoad(FirstN(d, num_remaining), d, pointer)
โหลดองค์ประกอบ num_remaining
แรกและส่งกลับค่าศูนย์ในเลนอื่น
นี่เป็นค่าเริ่มต้นที่ดีเมื่อเป็นไปไม่ได้ที่จะให้แน่ใจว่าเวกเตอร์นั้นมีเบาะ แต่จะปลอดภัยเท่านั้น #if !HWY_MEM_OPS_MIGHT_FAULT
! ตรงกันข้ามกับการวนซ้ำแบบสเกลาร์ จำเป็นต้องมีการวนซ้ำครั้งสุดท้ายเพียงครั้งเดียวเท่านั้น คาดว่าขนาดโค้ดที่เพิ่มขึ้นจากเนื้อหาลูปสองตัวจะคุ้มค่า เนื่องจากจะช่วยหลีกเลี่ยงค่าใช้จ่ายในการปิดบังทั้งหมดยกเว้นการวนซ้ำครั้งสุดท้าย
เราใช้ farm-sve โดย Berenger Bramas; ได้รับการพิสูจน์แล้วว่ามีประโยชน์สำหรับการตรวจสอบพอร์ต SVE บนเครื่องพัฒนา x86
นี่ไม่ใช่ผลิตภัณฑ์ของ Google ที่ได้รับการสนับสนุนอย่างเป็นทางการ ติดต่อ: [email protected]