Highway عبارة عن مكتبة C++ توفر جوهريات SIMD/ناقلات محمولة.
التوثيق
تم ترخيصه مسبقًا بموجب Apache 2، والآن تم ترخيصه بشكل مزدوج باسم Apache 2 / BSD-3.
نحن متحمسون للبرامج عالية الأداء. نحن نرى إمكانات كبيرة غير مستغلة في وحدات المعالجة المركزية (الخوادم، الأجهزة المحمولة، أجهزة الكمبيوتر المكتبية). الطريق السريع مخصص للمهندسين الذين يرغبون في دفع حدود ما هو ممكن في البرامج بشكل موثوق واقتصادي.
توفر وحدات المعالجة المركزية (CPU) تعليمات SIMD/المتجه التي تطبق نفس العملية على عناصر بيانات متعددة. وهذا يمكن أن يقلل من استخدام الطاقة على سبيل المثال خمسة أضعاف بسبب تنفيذ عدد أقل من التعليمات. غالبًا ما نرى أيضًا عمليات تسريع بمعدل 5 إلى 10x .
Highway يجعل برمجة SIMD/المتجه عملية وقابلة للتنفيذ وفقًا لهذه المبادئ التوجيهية:
يفعل ما تتوقعه : Highway عبارة عن مكتبة C++ تحتوي على وظائف مختارة بعناية والتي تتوافق بشكل جيد مع تعليمات وحدة المعالجة المركزية دون إجراء تحويلات شاملة للمترجم. يعد الكود الناتج أكثر قابلية للتنبؤ به وأقوى في تغييرات التعليمات البرمجية/تحديثات برنامج التحويل البرمجي من التحويل التلقائي.
يعمل على منصات مستخدمة على نطاق واسع : يدعم الطريق السريع خمسة أبنية؛ يمكن لنفس كود التطبيق أن يستهدف مجموعات تعليمات مختلفة، بما في ذلك تلك ذات المتجهات "القابلة للتطوير" (الحجم غير معروف في وقت الترجمة). يتطلب الطريق السريع C++ 11 فقط ويدعم أربع عائلات من المترجمين. إذا كنت ترغب في استخدام Highway على منصات أخرى، يرجى إثارة المشكلة.
مرونة النشر : يمكن تشغيل التطبيقات التي تستخدم Highway على السحابات غير المتجانسة أو أجهزة العميل، مع اختيار أفضل مجموعة تعليمات متاحة في وقت التشغيل. وبدلاً من ذلك، قد يختار المطورون استهداف مجموعة تعليمات واحدة دون أي تكاليف تشغيل إضافية. في كلتا الحالتين، رمز التطبيق هو نفسه باستثناء تبديل HWY_STATIC_DISPATCH
مع HWY_DYNAMIC_DISPATCH
بالإضافة إلى سطر واحد من التعليمات البرمجية. راجع أيضًا مقدمة @kfjahnke للإرسال.
مناسب لمجموعة متنوعة من المجالات : يوفر Highway مجموعة واسعة من العمليات المستخدمة لمعالجة الصور (النقطة العائمة)، والضغط، وتحليل الفيديو، والجبر الخطي، والتشفير، والفرز، والتوليد العشوائي. نحن ندرك أن حالات الاستخدام الجديدة قد تتطلب عمليات إضافية ويسعدنا إضافتها عندما يكون ذلك منطقيًا (على سبيل المثال، عدم وجود منحدرات في الأداء في بعض البنيات). اذا اردت المناقشة الرجاء رفع الموضوع
تصميم المكافآت المتوازي للبيانات : يوفر Highway أدوات مثل Gather وMaskedLoad وFixedTag لتمكين عمليات تسريع هياكل البيانات القديمة. ومع ذلك، يتم تحقيق أكبر المكاسب من خلال تصميم الخوارزميات وهياكل البيانات للمتجهات القابلة للتطوير. تتضمن الأساليب المفيدة التجميع، وتخطيطات بنية الصفيف، والتخصيصات المحاذاة/المبطنة.
نوصي بهذه الموارد للبدء:
العروض التوضيحية عبر الإنترنت باستخدام Compiler Explorer:
نلاحظ أن الطريق السريع تمت الإشارة إليه في المشاريع مفتوحة المصدر التالية، والتي يمكن العثور عليها عبر 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
(إصدار 2x غير مسجل من Wasm128، يتم تمكينه إذا تم تعريف HWY_WANT_WASM2
. وسيظل هذا مدعومًا حتى يتم استبداله بإصدار مستقبلي من WASM.)؛SSE2
SSSE3
(~إنتل كور)SSE4
(~Nehalem، ويتضمن أيضًا AES + CLMUL).AVX2
(~Haswell، يتضمن أيضًا BMI2 + F16 + FMA)AVX3
(~Skylake، AVX-512F/BW/CD/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
(~Sapphire Rapids، يتضمن AVX-512FP16)سياستنا هي أنه ما لم يتم تحديد خلاف ذلك، ستظل الأهداف مدعومة طالما أنه من الممكن تجميعها (عبر) باستخدام Clang أوGC المدعومة حاليًا، واختبارها باستخدام QEMU. إذا كان من الممكن تجميع الهدف باستخدام قناة LLVM واختباره باستخدام إصدارنا من QEMU بدون علامات إضافية، فهو مؤهل للتضمين في البنية التحتية للاختبار المستمر لدينا. بخلاف ذلك، سيتم اختبار الهدف يدويًا قبل الإصدارات باستخدام الإصدارات/التكوينات المحددة لـ Clang وGC.
تم اختبار SVE في البداية باستخدام Farm_sve (انظر الإقرارات).
تهدف إصدارات Highway إلى اتباع نظام semver.org (MAJOR.MINOR.PATCH)، وزيادة MINOR بعد الإضافات المتوافقة مع الإصدارات السابقة وPATCH بعد الإصلاحات المتوافقة مع الإصدارات السابقة. نوصي باستخدام الإصدارات (بدلاً من نصيحة Git) لأنها تخضع للاختبار على نطاق أوسع، انظر أدناه.
يشير الإصدار الحالي 1.0 إلى زيادة التركيز على التوافق مع الإصدارات السابقة. ستظل التطبيقات التي تستخدم الوظائف الموثقة متوافقة مع التحديثات المستقبلية التي لها نفس رقم الإصدار الرئيسي.
يتم إنشاء اختبارات التكامل المستمر باستخدام إصدار حديث من Clang (يعمل على الإصدار x86 الأصلي، أو QEMU لـ RISC-V وArm) وMSVC 2019 (الإصدار 19.28، الذي يعمل على الإصدار x86 الأصلي).
قبل الإصدارات، قمنا أيضًا باختبار الإصدار x86 باستخدام Clang وGC، وArmv7/8 عبر التجميع المتقاطع لدول مجلس التعاون الخليجي. راجع عملية الاختبار للحصول على التفاصيل.
يحتوي دليل contrib
على أدوات مساعدة متعلقة بـ SIMD: فئة صور ذات صفوف محاذية، ومكتبة رياضية (16 وظيفة تم تنفيذها بالفعل، معظمها علم المثلثات)، ووظائف لحساب المنتجات النقطية والفرز.
إذا كنت تحتاج فقط إلى دعم x86، فيمكنك أيضًا استخدام مكتبة فئات المتجهات VCL الخاصة بـ Agner Fog. ويشمل العديد من الوظائف بما في ذلك مكتبة الرياضيات كاملة.
إذا كان لديك كود موجود يستخدم جوهريات x86/NEON، فقد تكون مهتمًا بـ SIMDe، الذي يحاكي تلك الجوهريات باستخدام جوهريات الأنظمة الأساسية الأخرى أو التحويل التلقائي.
يستخدم هذا المشروع CMake للتوليد والبناء. في النظام المبني على دبيان، يمكنك تثبيته عبر:
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.
يتطلب تشغيل الاختبارات المجمعة دعمًا من نظام التشغيل، والذي يتم توفيره على دبيان بواسطة الحزمة 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. نحن نفهم أن العمل جار لإزالة هذا القيد.
البناء على الإصدار 32 بت x86 غير مدعوم رسميًا، ويتم تعطيل AVX2/3 افتراضيًا هناك. لاحظ أن johnplatts قد نجح في إنشاء وتشغيل اختبارات Highway على الإصدار 32 بت x86، بما في ذلك AVX2/3، وعلى دول مجلس التعاون الخليجي 7/8 وClang 8/11/12. في Ubuntu 22.04، يتطلب Clang 11 و12، ولكن ليس الإصدارات الأحدث، إشارات مترجم إضافية -m32 -isystem /usr/i686-linux-gnu/include
. يتطلب Clang 10 والإصدارات الأقدم ما ورد أعلاه بالإضافة إلى -isystem /usr/i686-linux-gnu/include/c++/12/i686-linux-gnu
. انظر رقم 1279.
الطريق السريع متاح الآن في vcpkg
vcpkg install highway
يتم تحديث منفذ الطريق السريع في vcpkg بواسطة أعضاء فريق Microsoft والمساهمين في المجتمع. إذا كان الإصدار قديمًا، فيرجى إنشاء مشكلة أو سحب طلب على مستودع vcpkg.
يمكنك استخدام benchmark
الموجود داخل الأمثلة/ كنقطة بداية.
تسرد الصفحة المرجعية السريعة جميع العمليات ومعلماتها بإيجاز، وتشير التعليمات_matrix إلى عدد التعليمات لكل عملية.
تجيب الأسئلة الشائعة على أسئلة حول إمكانية النقل وتصميم واجهة برمجة التطبيقات (API) ومكان العثور على مزيد من المعلومات.
نوصي باستخدام متجهات SIMD الكاملة كلما أمكن ذلك لتحقيق أقصى قدر من إمكانية نقل الأداء. للحصول عليها، قم بتمرير علامة ScalableTag
(أو ما يعادلها HWY_FULL(float)
) إلى وظائف مثل Zero/Set/Load
. هناك بديلان لحالات الاستخدام التي تتطلب حدًا أعلى للممرات:
بالنسبة لما يصل إلى N
من الممرات، حدد CappedTag
أو ما يعادله HWY_CAPPED(T, N)
. سيتم تقريب العدد الفعلي للممرات إلى N
إلى أقرب قوة اثنين، مثل 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 عند استخدام إرسال وقت التشغيل ويختار المترجم مُهيئًا تم تجميعه لهدف غير مدعوم من وحدة المعالجة المركزية الحالية. بدلاً من ذلك، يجب أن تكون الثوابت التي تتم تهيئتها عبر Set
بشكل عام متغيرات محلية (const).
تختلف نقاط الإدخال إلى التعليمات البرمجية باستخدام الطريق السريع قليلاً اعتمادًا على ما إذا كانت تستخدم الإرسال الثابت أو الديناميكي. في كلتا الحالتين، نوصي بأن تتلقى وظيفة المستوى الأعلى مؤشرًا واحدًا أو أكثر إلى المصفوفات، بدلاً من أنواع المتجهات الخاصة بالهدف.
بالنسبة للإرسال الثابت، سيكون HWY_TARGET
أفضل هدف متاح بين HWY_BASELINE_TARGETS
، أي تلك المسموح باستخدامها بواسطة المترجم (راجع المرجع السريع). يمكن استدعاء الوظائف داخل HWY_NAMESPACE
باستخدام HWY_STATIC_DISPATCH(func)(args)
داخل نفس الوحدة التي تم تعريفها فيها. يمكنك استدعاء الوظيفة من وحدات أخرى عن طريق تغليفها في وظيفة عادية وإعلان الوظيفة العادية في الرأس.
للإرسال الديناميكي، يتم إنشاء جدول مؤشرات الوظائف عبر الماكرو HWY_EXPORT
الذي يستخدمه HWY_DYNAMIC_DISPATCH(func)(args)
لاستدعاء أفضل مؤشر دالة للأهداف المدعومة لوحدة المعالجة المركزية الحالية. يتم تجميع الوحدة تلقائيًا لكل هدف في HWY_TARGETS
(راجع المرجع السريع) إذا تم تعريف HWY_TARGET_INCLUDE
وتم تضمين foreach_target.h
. لاحظ أن الاستدعاء الأول لـ HWY_DYNAMIC_DISPATCH
، أو كل استدعاء للمؤشر يتم إرجاعه بواسطة الاستدعاء الأول لـ HWY_DYNAMIC_POINTER
، يتضمن بعض الحمل الزائد للكشف عن وحدة المعالجة المركزية. يمكنك منع ذلك عن طريق استدعاء ما يلي قبل أي استدعاء لـ 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. بالنسبة لـ clang وGC، يكون -O2
كافيًا بشكل عام.
بالنسبة لـ MSVC، نوصي بالتجميع باستخدام /Gv
للسماح للوظائف غير المضمنة بتمرير وسيطات المتجهات في السجلات. إذا كنت تنوي استخدام هدف AVX2 مع متجهات نصف العرض (على سبيل المثال لـ PromoteTo
)، فمن المهم أيضًا التجميع باستخدام /arch:AVX2
. يبدو أن هذه هي الطريقة الوحيدة لإنشاء تعليمات SSE مشفرة بـ VEX على MSVC بشكل موثوق. في بعض الأحيان يقوم MSVC بإنشاء تعليمات SSE مشفرة بـ VEX، إذا تم مزجها مع AVX، ولكن ليس دائمًا، راجع DevCom-10618264. وبخلاف ذلك، قد يؤدي خلط تعليمات AVX2 المشفرة بـ VEX مع تعليمات SSE غير VEX إلى تدهور شديد في الأداء. لسوء الحظ، مع خيار /arch:AVX2
، سيتطلب الملف الثنائي الناتج AVX2. لاحظ أنه ليست هناك حاجة لمثل هذه العلامة لـ clang وGC لأنها تدعم السمات الخاصة بالهدف، والتي نستخدمها لضمان إنشاء كود VEX المناسب لأهداف AVX2.
عند توجيه حلقة، السؤال المهم هو ما إذا كان سيتم التعامل مع عدد من التكرارات ('عدد الرحلات'، يُشار إليه count
) وكيفية التعامل معها والتي لا تقسم حجم المتجه بالتساوي N = Lanes(d)
. على سبيل المثال، قد يكون من الضروري تجنب الكتابة بعد نهاية المصفوفة.
في هذا القسم، دع T
يشير إلى نوع العنصر و d = ScalableTag
. افترض أن نص الحلقة معطى template
.
"التعدين الشريطي" هو أسلوب لتوجيه حلقة عن طريق تحويلها إلى حلقة خارجية وحلقة داخلية، بحيث يتطابق عدد التكرارات في الحلقة الداخلية مع عرض المتجه. ثم يتم استبدال الحلقة الداخلية بعمليات المتجهات.
يقدم الطريق السريع عدة إستراتيجيات لتوجيه الحلقات:
تأكد من أن جميع المدخلات والمخرجات مبطنة. ثم الحلقة (الخارجية) هي ببساطة
for (size_t i = 0; i < count; i += N) LoopBody(d, i, 0);
هنا، ليست هناك حاجة إلى معلمة القالب ووسيطة الوظيفة الثانية.
هذا هو الخيار المفضل، ما لم يكن N
بالآلاف ويتم توصيل عمليات المتجهات بفترات استجابة طويلة. كان هذا هو الحال بالنسبة لأجهزة الكمبيوتر العملاقة في التسعينيات، ولكن في الوقت الحاضر أصبحت وحدات ALU رخيصة ونرى أن معظم التطبيقات تقسم المتجهات إلى جزء واحد أو جزءين أو أربعة أجزاء، لذلك هناك تكلفة قليلة لمعالجة المتجهات بأكملها حتى لو لم نكن بحاجة إلى جميع مساراتها. في الواقع، يؤدي هذا إلى تجنب التكلفة (المحتملة الكبيرة) للتنبؤ أو التحميل/التخزين الجزئي على الأهداف القديمة، ولا يكرر التعليمات البرمجية.
معالجة المتجهات بأكملها وتضمين العناصر التي تمت معالجتها مسبقًا في المتجه الأخير:
for (size_t i = 0; i < count; i += N) LoopBody(d, HWY_MIN(i, count - N), 0);
هذا هو الخيار المفضل الثاني بشرط أن يكون count >= N
وأن يكون LoopBody
غير فعال. قد تتم معالجة بعض العناصر مرتين، ولكن عادةً ما يكون من المفيد استخدام مسار تعليمات برمجية واحد وتوجيه كامل. حتى لو كان 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
! على النقيض من الحلقة العددية، هناك حاجة إلى تكرار نهائي واحد فقط. من المتوقع أن يكون حجم الكود المتزايد من هيئتي الحلقة مفيدًا لأنه يتجنب تكلفة الإخفاء في جميع التكرارات باستثناء التكرار النهائي.
لقد استخدمنا مزرعة sve بواسطة Berenger Bramas؛ لقد أثبت أنه مفيد للتحقق من منفذ SVE على جهاز تطوير x86.
هذا ليس أحد منتجات Google المدعومة رسميًا. جهة الاتصال: [email protected]