x86 মেশিন কোড (128-বিট এসএসই 1 এবং এভিএক্স ব্যবহার করে সিমডি 4x ফ্লোট) 94 বাইট
x86 মেশিন কোড (256-বিট AVX ব্যবহার করে সিমডি 4x ডাবল) 123 বাইট
float
পরীক্ষায় পরীক্ষাগুলি কেস পাস করে তবে লুপ-প্রস্থান থ্রেশহোল্ডটি ঘটানোর পক্ষে যথেষ্ট ছোট হয়ে গেলে এলোমেলো ইনপুট সহ অসীম লুপে আটকা পড়া সহজ।
এসএসই 1 প্যাকড-একক-নির্ভুল নির্দেশাবলী 3 বাইট দীর্ঘ, তবে এসএসই 2 এবং সাধারণ অ্যাভিএক্স নির্দেশাবলী 4 বাইট দীর্ঘ। (স্কেলারের একক নির্দেশনাগুলিও sqrtss
4 বাইট দীর্ঘ দীর্ঘ, যার কারণে sqrtps
আমি কেবলমাত্র কম উপাদান সম্পর্কে যত্নশীল যদিও আমি এটি ব্যবহার করি modern এটি আধুনিক হার্ডওয়্যারের স্কয়ার্সের চেয়েও ধীর নয়)। আমি 2 বাইট বনাম মুভাপস + অপের সংরক্ষণ করতে অ-ধ্বংসাত্মক গন্তব্যের জন্য AVX ব্যবহার করেছি।
ডাবল সংস্করণে আমরা এখনও movlhps
couple৪-বিট খণ্ডগুলি অনুলিপি করতে একটি দম্পতি করতে পারি (কারণ প্রায়শই আমরা কেবল অনুভূমিক সমষ্টিগুলির নিম্ন উপাদানকেই যত্ন করি)। 256-বিট সিমডি ভেক্টরের আনুভূমিক যোগফলের জন্য vextractf128
উচ্চতর অর্ধেক পেতে বনাম, ভাসমানের জন্য ধীর অথচ ছোট 2 এক্স haddps
কৌশলটি পেতে অতিরিক্ত অতিরিক্ত প্রয়োজন । দ্যdouble
সংস্করণে 2x 4-বাইটের পরিবর্তে 2x 8-বাইট ধ্রুবক প্রয়োজন। সামগ্রিকভাবে এটি float
সংস্করণটির আকারের 4/3 এর কাছাকাছি আসে ।
mean(a,b) = mean(a,a,b,b)
এগুলির 4 টির জন্য , সুতরাং আমরা 4 টি উপাদান অবধি ইনপুটটিকে নকল করতে পারি এবং কখনও দৈর্ঘ্য = 2 প্রয়োগ করতে হয় না। সুতরাং আমরা উদাহরণস্বরূপ, জ্যামিতিক গড়কে 4 র্থ-রুট = স্কয়ার্ট (স্কয়ার্ট) হিসাবে হার্ডকোড করতে পারি। এবং আমাদের কেবল একটি এফপি ধ্রুবক প্রয়োজন 4.0
,।
আমাদের চারটির একক সিমডি ভেক্টর রয়েছে [a_i, b_i, c_i, d_i]
। এর থেকে, আমরা 4 টি পৃথক রেজিস্টারে স্কেলার হিসাবে গণনা করি এবং পরবর্তী পুনরাবৃত্তির জন্য এগুলি একসাথে ফিরিয়ে আনি। (সিমডি ভেক্টরগুলিতে অনুভূমিক অপারেশনগুলি অসুবিধাজনক, তবে পর্যাপ্ত ক্ষেত্রে এটি 4 টি উপাদানগুলির জন্য আমাদের একই জিনিস করা দরকার যা এটি ভারসাম্যপূর্ণ।
এর লুপ-প্রস্থান শর্তটি }while(quadratic - harmonic > 4e-5)
(বা এর জন্য আরও ছোট ধ্রুবক double
) @ রবিনরাইডারের আর উত্তর , এবং কেভিন ক্রুইজসেনের জাভা উত্তরের উপর ভিত্তি করে : চতুর্ভুজ গড় সর্বদা সর্বকালের বৃহত্তম মাত্রা, এবং সুরেলা গড় সর্বদা সবচেয়ে ক্ষুদ্রতম (গোলাকার ত্রুটি উপেক্ষা করে)। সুতরাং আমরা রূপান্তর সনাক্ত করতে এই দুটিয়ের মধ্যে ব-দ্বীপটি পরীক্ষা করতে পারি। আমরা গাণিতিক ফলাফলটি স্কেলারের ফলাফল হিসাবে ফিরে পাই। এটি সাধারণত এই দুজনের মধ্যে এবং সম্ভবত গোলাকার ত্রুটিগুলির মধ্যে সবচেয়ে কম সংবেদনশীল।
ফ্লোট সংস্করণ : float meanmean_float_avx(__m128);
আরজি এবং xmm0 এ রিটার্নের মান হিসাবে কল করা যায়। (সুতরাং x86-64 সিস্টেম ভি, বা উইন্ডোজ এক্স 6464 ভেক্টরক্যাল, তবে x64 ফাস্টক্যাল নয়)) অথবা রিটার্ন-টাইপটি ঘোষণা করুন __m128
যাতে আপনি পরীক্ষার জন্য চতুর্ভুজ এবং সুরেলা গড়তে পারেন।
float
এটি xmm0 এবং xmm1 এ 2 পৃথক আরোগুলি গ্রহণের জন্য 1 অতিরিক্ত বাইট লাগবে: আমাদের shufps
একসাথে ঝাঁকুনির unpcklps xmm0,xmm0
জন্য এবং 2 টি ইনপুটগুলি নকল করতে একটি প্রতিরোধী 8 (কেবলমাত্র পরিবর্তে ) প্রয়োজন।
40 address align 32
41 code bytes global meanmean_float_avx
42 meanmean_float_avx:
43 00000000 B9[52000000] mov ecx, .arith_mean ; allows 2-byte call reg, and a base for loading constants
44 00000005 C4E2791861FC vbroadcastss xmm4, [rcx-4] ; float 4.0
45
46 ;; mean(a,b) = mean(a,b,a,b) for all 4 types of mean
47 ;; so we only ever have to do the length=4 case
48 0000000B 0F14C0 unpcklps xmm0,xmm0 ; [b,a] => [b,b,a,a]
49
50 ; do{ ... } while(quadratic - harmonic > threshold);
51 .loop:
52 ;;; XMM3 = geometric mean: not based on addition. (Transform to log would be hard. AVX512ER has exp with 23-bit accuracy, but not log. vgetexp = floor(lofg2(x)), so that's no good.)
53 ;; sqrt once *first*, making magnitudes closer to 1.0 to reduce rounding error. Numbers are all positive so this is safe.
54 ;; both sqrts first was better behaved, I think.
55 0000000E 0F51D8 sqrtps xmm3, xmm0 ; xmm3 = 4th root(x)
56 00000011 F30F16EB movshdup xmm5, xmm3 ; bring odd elements down to even
57 00000015 0F59EB mulps xmm5, xmm3
58 00000018 0F12DD movhlps xmm3, xmm5 ; high half -> low
59 0000001B 0F59DD mulps xmm3, xmm5 ; xmm3[0] = hproduct(sqrt(xmm))
60 ; sqrtps xmm3, xmm3 ; sqrt(hprod(sqrt)) = 4th root(hprod)
61 ; common final step done after interleaving with quadratic mean
62
63 ;;; XMM2 = quadratic mean = max of the means
64 0000001E C5F859E8 vmulps xmm5, xmm0,xmm0
65 00000022 FFD1 call rcx ; arith mean of squares
66 00000024 0F14EB unpcklps xmm5, xmm3 ; [quad^2, geo^2, ?, ?]
67 00000027 0F51D5 sqrtps xmm2, xmm5 ; [quad, geo, ?, ?]
68
69 ;;; XMM1 = harmonic mean = min of the means
70 0000002A C5D85EE8 vdivps xmm5, xmm4, xmm0 ; 4/x
71 0000002E FFD1 call rcx ; arithmetic mean (under inversion)
72 00000030 C5D85ECD vdivps xmm1, xmm4, xmm5 ; 4/. (the factor of 4 cancels out)
73
74 ;;; XMM5 = arithmetic mean
75 00000034 0F28E8 movaps xmm5, xmm0
76 00000037 FFD1 call rcx
77
78 00000039 0F14E9 unpcklps xmm5, xmm1 ; [arith, harm, ?,?]
79 0000003C C5D014C2 vunpcklps xmm0, xmm5,xmm2 ; x = [arith, harm, quad, geo]
80
81 00000040 0F5CD1 subps xmm2, xmm1 ; largest - smallest mean: guaranteed non-negative
82 00000043 0F2E51F8 ucomiss xmm2, [rcx-8] ; quad-harm > convergence_threshold
83 00000047 73C5 jae .loop
84
85 ; return with the arithmetic mean in the low element of xmm0 = scalar return value
86 00000049 C3 ret
87
88 ;;; "constant pool" between the main function and the helper, like ARM literal pools
89 0000004A ACC52738 .fpconst_threshold: dd 4e-5 ; 4.3e-5 is the highest we can go and still pass the main test cases
90 0000004E 00008040 .fpconst_4: dd 4.0
91 .arith_mean: ; returns XMM5 = hsum(xmm5)/4.
92 00000052 C5D37CED vhaddps xmm5, xmm5 ; slow but small
93 00000056 C5D37CED vhaddps xmm5, xmm5
94 0000005A 0F5EEC divps xmm5, xmm4 ; divide before/after summing doesn't matter mathematically or numerically; divisor is a power of 2
95 0000005D C3 ret
96 0000005E 5E000000 .size: dd $ - meanmean_float_avx
0x5e = 94 bytes
(এর সাথে এনএএসএম তালিকা তৈরি করা হয়েছে nasm -felf64 mean-mean.asm -l/dev/stdout | cut -b -34,$((34+6))-
। তালিকার অংশটি স্ট্রিপ করুন এবং উত্সটি পুনরুদ্ধার করুন cut -b 34- > mean-mean.asm
)
সিমড অনুভূমিক যোগফল এবং 4 দ্বারা ভাগ (অর্থাত্ গাণিতিক গড়) একটি পৃথক ফাংশনে প্রয়োগ করা হয় যা আমরা call
(ঠিকানার ব্যয়কে আরও বাড়ানোর জন্য একটি ফাংশন পয়েন্টার সহ) করি। এর 4/x
আগে / পরে, বা এর x^2
আগে এবং পরে স্ক্যুআরটি, আমরা সুরেলা ও চতুষ্কোণিক গড় পাই। ( div
সঠিকভাবে উপস্থাপনযোগ্য দ্বারা গুণ করার পরিবর্তে এই নির্দেশাবলীটি লিখতে কষ্ট হয়েছে was 0.25
)
জ্যামিতিক গড়টি পৃথকভাবে গুণিত এবং চেইন স্কয়ারটি দিয়ে প্রয়োগ করা হয়। বা একক স্কয়ার্টের সাহায্যে প্রথমে ব্যয়বৃত্তির প্রবণতা হ্রাস করতে এবং সংখ্যাসম্য নির্ভুলভাবে সহায়তা করতে পারে। লগ শুধুমাত্র উপলব্ধ নয় floor(log2(x))
AVX512 মাধ্যমে vgetexpps/pd
। এক্সপ AVX512ER (কেবলমাত্র জিওন ফি) এর মাধ্যমে পাওয়া যায় তবে কেবল 2 ^ -23 নির্ভুলতা সহ।
128-বিট এভিএক্স নির্দেশাবলী এবং লিগ্যাসি এসএসই মেশানো কোনও পারফরম্যান্স সমস্যা নয়। উত্তরাধিকারী এসএসইর সাথে 256-বিট এভিএক্স মিশ্রণ হাসওলে হতে পারে তবে স্কাইলেকে এটি এসএসই নির্দেশাবলীর জন্য সম্ভাব্য মিথ্যা নির্ভরতা তৈরি করে। আমি মনে করি আমার double
সংস্করণটি কোনও অপ্রয়োজনীয় লুপ বহনকারী ডিপ চেইনগুলি এবং ডিভ / স্কয়ার্ট ল্যাটেন্সি / থ্রুপুটগুলিতে বাধাগুলি এড়িয়ে চলে।
ডাবল সংস্করণ:
108 global meanmean_double_avx
109 meanmean_double_avx:
110 00000080 B9[E8000000] mov ecx, .arith_mean
111 00000085 C4E27D1961F8 vbroadcastsd ymm4, [rcx-8] ; float 4.0
112
113 ;; mean(a,b) = mean(a,b,a,b) for all 4 types of mean
114 ;; so we only ever have to do the length=4 case
115 0000008B C4E37D18C001 vinsertf128 ymm0, ymm0, xmm0, 1 ; [b,a] => [b,a,b,a]
116
117 .loop:
118 ;;; XMM3 = geometric mean: not based on addition.
119 00000091 C5FD51D8 vsqrtpd ymm3, ymm0 ; sqrt first to get magnitude closer to 1.0 for better(?) numerical precision
120 00000095 C4E37D19DD01 vextractf128 xmm5, ymm3, 1 ; extract high lane
121 0000009B C5D159EB vmulpd xmm5, xmm3
122 0000009F 0F12DD movhlps xmm3, xmm5 ; extract high half
123 000000A2 F20F59DD mulsd xmm3, xmm5 ; xmm3 = hproduct(sqrt(xmm0))
124 ; sqrtsd xmm3, xmm3 ; xmm3 = 4th root = geomean(xmm0) ;deferred until quadratic
125
126 ;;; XMM2 = quadratic mean = max of the means
127 000000A6 C5FD59E8 vmulpd ymm5, ymm0,ymm0
128 000000AA FFD1 call rcx ; arith mean of squares
129 000000AC 0F16EB movlhps xmm5, xmm3 ; [quad^2, geo^2]
130 000000AF 660F51D5 sqrtpd xmm2, xmm5 ; [quad , geo]
131
132 ;;; XMM1 = harmonic mean = min of the means
133 000000B3 C5DD5EE8 vdivpd ymm5, ymm4, ymm0 ; 4/x
134 000000B7 FFD1 call rcx ; arithmetic mean under inversion
135 000000B9 C5DB5ECD vdivsd xmm1, xmm4, xmm5 ; 4/. (the factor of 4 cancels out)
136
137 ;;; XMM5 = arithmetic mean
138 000000BD C5FC28E8 vmovaps ymm5, ymm0
139 000000C1 FFD1 call rcx
140
141 000000C3 0F16E9 movlhps xmm5, xmm1 ; [arith, harm]
142 000000C6 C4E35518C201 vinsertf128 ymm0, ymm5, xmm2, 1 ; x = [arith, harm, quad, geo]
143
144 000000CC C5EB5CD1 vsubsd xmm2, xmm1 ; largest - smallest mean: guaranteed non-negative
145 000000D0 660F2E51F0 ucomisd xmm2, [rcx-16] ; quad - harm > threshold
146 000000D5 77BA ja .loop
147
148 ; vzeroupper ; not needed for correctness, only performance
149 ; return with the arithmetic mean in the low element of xmm0 = scalar return value
150 000000D7 C3 ret
151
152 ; "literal pool" between the function
153 000000D8 95D626E80B2E113E .fpconst_threshold: dq 1e-9
154 000000E0 0000000000001040 .fpconst_4: dq 4.0 ; TODO: golf these zeros? vpbroadcastb and convert?
155 .arith_mean: ; returns YMM5 = hsum(ymm5)/4.
156 000000E8 C4E37D19EF01 vextractf128 xmm7, ymm5, 1
157 000000EE C5D158EF vaddpd xmm5, xmm7
158 000000F2 C5D17CED vhaddpd xmm5, xmm5 ; slow but small
159 000000F6 C5D35EEC vdivsd xmm5, xmm4 ; only low element matters
160 000000FA C3 ret
161 000000FB 7B000000 .size: dd $ - meanmean_double_avx
0x7b = 123 bytes
সি পরীক্ষার জোতা
#include <immintrin.h>
#include <stdio.h>
#include <math.h>
static const struct ab_avg {
double a,b;
double mean;
} testcases[] = {
{1, 1, 1},
{1, 2, 1.45568889},
{100, 200, 145.568889},
{2.71, 3.14, 2.92103713},
{0.57, 1.78, 1.0848205},
{1.61, 2.41, 1.98965438},
{0.01, 100, 6.7483058},
};
// see asm comments for order of arith, harm, quad, geo
__m128 meanmean_float_avx(__m128); // or float ...
__m256d meanmean_double_avx(__m128d); // or double ...
int main(void) {
int len = sizeof(testcases) / sizeof(testcases[0]);
for(int i=0 ; i<len ; i++) {
const struct ab_avg *p = &testcases[i];
#if 1
__m128 arg = _mm_set_ps(0,0, p->b, p->a);
double res = meanmean_float_avx(arg)[0];
#else
__m128d arg = _mm_loadu_pd(&p->a);
double res = meanmean_double_avx(arg)[0];
#endif
double allowed_diff = (p->b - p->a) / 100000.0;
double delta = fabs(p->mean - res);
if (delta > 1e-3 || delta > allowed_diff) {
printf("%f %f => %.9f but we got %.9f. delta = %g allowed=%g\n",
p->a, p->b, p->mean, res, p->mean - res, allowed_diff);
}
}
while(1) {
double a = drand48(), b = drand48(); // range= [0..1)
if (a>b) {
double tmp=a;
a=b;
b=tmp; // sorted
}
// a *= 0.00000001;
// b *= 123156;
// a += 1<<11; b += (1<<12)+1; // float version gets stuck inflooping on 2048.04, 4097.18 at fpthreshold = 4e-5
// a *= 1<<11 ; b *= 1<<11; // scaling to large magnitude makes sum of squares loses more precision
//a += 1<<11; b+= 1<<11; // adding to large magnitude is hard for everything, catastrophic cancellation
#if 1
printf("testing float %g, %g\n", a, b);
__m128 arg = _mm_set_ps(0,0, b, a);
__m128 res = meanmean_float_avx(arg);
double quad = res[2], harm = res[1]; // same order as double... for now
#else
printf("testing double %g, %g\n", a, b);
__m128d arg = _mm_set_pd(b, a);
__m256d res = meanmean_double_avx(arg);
double quad = res[2], harm = res[1];
#endif
double delta = fabs(quad - harm);
double allowed_diff = (b - a) / 100000.0; // calculated in double even for the float case.
// TODO: use the double res as a reference for float res
// instead of just checking quadratic vs. harmonic mean
if (delta > 1e-3 || delta > allowed_diff) {
printf("%g %g we got q=%g, h=%g, a=%g. delta = %g, allowed=%g\n",
a, b, quad, harm, res[0], quad-harm, allowed_diff);
}
}
}
এর সাথে বিল্ড করুন:
nasm -felf64 mean-mean.asm &&
gcc -no-pie -fno-pie -g -O2 -march=native mean-mean.c mean-mean.o
স্পষ্টতই আপনার এভিএক্স সমর্থন সহ একটি সিপিইউ বা ইনটেল এসডিইয়ের মতো এমুলেটর দরকার। নেটিভ অ্যাভিএক্স সমর্থন ব্যতীত কোনও হোস্টে সংকলন করতে, ব্যবহার করুন -march=sandybridge
বা-mavx
রান: হার্ড-কোডেড পরীক্ষার কেসগুলি পাস করে তবে ফ্লোট সংস্করণের জন্য, এলোমেলো পরীক্ষার ক্ষেত্রে প্রায়শই (b-a)/10000
প্রশ্নটির প্রান্তিক সেট ব্যর্থ হয় ।
$ ./a.out
(note: empty output before the first "testing float" means clean pass on the constant test cases)
testing float 3.90799e-14, 0.000985395
3.90799e-14 0.000985395 we got q=3.20062e-10, h=3.58723e-05, a=2.50934e-05. delta = -3.5872e-05, allowed=9.85395e-09
testing float 0.041631, 0.176643
testing float 0.0913306, 0.364602
testing float 0.0922976, 0.487217
testing float 0.454433, 0.52675
0.454433 0.52675 we got q=0.48992, h=0.489927, a=0.489925. delta = -6.79493e-06, allowed=7.23169e-07
testing float 0.233178, 0.831292
testing float 0.56806, 0.931731
testing float 0.0508319, 0.556094
testing float 0.0189148, 0.767051
0.0189148 0.767051 we got q=0.210471, h=0.210484, a=0.21048. delta = -1.37389e-05, allowed=7.48136e-06
testing float 0.25236, 0.298197
0.25236 0.298197 we got q=0.274796, h=0.274803, a=0.274801. delta = -6.19888e-06, allowed=4.58374e-07
testing float 0.531557, 0.875981
testing float 0.515431, 0.920261
testing float 0.18842, 0.810429
testing float 0.570614, 0.886314
testing float 0.0767746, 0.815274
testing float 0.118352, 0.984891
0.118352 0.984891 we got q=0.427845, h=0.427872, a=0.427863. delta = -2.66135e-05, allowed=8.66539e-06
testing float 0.784484, 0.893906
0.784484 0.893906 we got q=0.838297, h=0.838304, a=0.838302. delta = -7.09295e-06, allowed=1.09422e-06
এফপি ত্রুটিগুলি যথেষ্ট যে কিছু ইনপুটগুলির জন্য কোয়াড-ক্ষতি শূন্যের চেয়ে কম আসে।
বা a += 1<<11; b += (1<<12)+1;
নিঃশর্ত সহ:
testing float 2048, 4097
testing float 2048.04, 4097.18
^C (stuck in an infinite loop).
এই সমস্যাগুলির সাথে কোনওটিই ঘটে না double
। printf
প্রতিটি পরীক্ষার আগে মন্তব্য করে দেখুন আউটপুট খালি আছে ( if(delta too high)
ব্লক থেকে কিছুই নেই )।
টোডো: double
সংস্করণটির জন্য রেফারেন্স হিসাবে সংস্করণটি ব্যবহার করুন float
, কেবল কীভাবে তারা কোয়াড-ক্ষতির সাথে রূপান্তর করছে তা দেখার পরিবর্তে।