मैं एक गणना-गहन एल्गोरिदम अनुकूलित करने की कोशिश कर रहा हूं और कुछ कैश समस्या पर फंस गया हूं। मेरे पास एक बड़ा बफर है जो कभी-कभी और यादृच्छिक रूप से लिखा जाता है और एप्लिकेशन के अंत में केवल एक बार पढ़ा जाता है। जाहिर है, बफर में लिखने से बहुत सारे कैश मिस पैदा होते हैं और कैशों को प्रदूषित करने के बाद बाद में गणना के लिए जरूरी होता है। मैंने गैर-अस्थायी चाल instrinsics का उपयोग करने की कोशिश की, लेकिन कैश याद आती है (valgrind द्वारा रिपोर्ट और रनटाइम माप द्वारा समर्थित) अभी भी होता है। हालांकि, गैर-अस्थायी चालों की और जांच करने के लिए, मैंने एक छोटा परीक्षण कार्यक्रम लिखा, जिसे आप नीचे देख सकते हैं। अनुक्रमिक पहुंच, बड़े बफर, केवल लिखते हैं।_mm_stream_ps एल 1/एलएल कैश का उत्पादन क्यों करता है?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <smmintrin.h>
void tim(const char *name, void (*func)()) {
struct timespec t1, t2;
clock_gettime(CLOCK_REALTIME, &t1);
func();
clock_gettime(CLOCK_REALTIME, &t2);
printf("%s : %f s.\n", name, (t2.tv_sec - t1.tv_sec) + (float) (t2.tv_nsec - t1.tv_nsec)/1000000000);
}
const int CACHE_LINE = 64;
const int FACTOR = 1024;
float *arr;
int length;
void func1() {
for(int i = 0; i < length; i++) {
arr[i] = 5.0f;
}
}
void func2() {
for(int i = 0; i < length; i += 4) {
arr[i] = 5.0f;
arr[i+1] = 5.0f;
arr[i+2] = 5.0f;
arr[i+3] = 5.0f;
}
}
void func3() {
__m128 buf = _mm_setr_ps(5.0f, 5.0f, 5.0f, 5.0f);
for(int i = 0; i < length; i += 4) {
_mm_stream_ps(&arr[i], buf);
}
}
void func4() {
__m128 buf = _mm_setr_ps(5.0f, 5.0f, 5.0f, 5.0f);
for(int i = 0; i < length; i += 16) {
_mm_stream_ps(&arr[i], buf);
_mm_stream_ps(&arr[4], buf);
_mm_stream_ps(&arr[8], buf);
_mm_stream_ps(&arr[12], buf);
}
}
int main() {
length = CACHE_LINE * FACTOR * FACTOR;
arr = malloc(length * sizeof(float));
tim("func1", func1);
free(arr);
arr = malloc(length * sizeof(float));
tim("func2", func2);
free(arr);
arr = malloc(length * sizeof(float));
tim("func3", func3);
free(arr);
arr = malloc(length * sizeof(float));
tim("func4", func4);
free(arr);
return 0;
}
फ़ंक्शन 1 बेवकूफ दृष्टिकोण है, फ़ंक्शन 2 लूप अनोलिंग का उपयोग करता है। फंक्शन 3 movntps का उपयोग करता है, जो वास्तव में कम से कम असेंबली में डाला गया था जब मैंने -O0 की जांच की थी। फंक्शन 4 में मैंने CPU को लिखने में मदद करने के लिए एक बार में कई movntps निर्देश जारी करने का प्रयास किया। मैंने gcc -g -lrt -std=gnu99 -OX -msse4.1 test.c
के साथ कोड संकलित किया जहां X
[0..3] में से एक है। परिणाम हैं .. कहने के लिए दिलचस्प पर सबसे अच्छा:
-O0
func1 : 0.407794 s.
func2 : 0.320891 s.
func3 : 0.161100 s.
func4 : 0.401755 s.
-O1
func1 : 0.194339 s.
func2 : 0.182536 s.
func3 : 0.101712 s.
func4 : 0.383367 s.
-O2
func1 : 0.108488 s.
func2 : 0.088826 s.
func3 : 0.101377 s.
func4 : 0.384106 s.
-O3
func1 : 0.078406 s.
func2 : 0.084927 s.
func3 : 0.102301 s.
func4 : 0.383366 s.
आप _mm_stream_ps देख सकते हैं दूसरों जब कार्यक्रम जीसीसी से अनुकूल नहीं है लेकिन फिर काफी अपने उद्देश्य में विफल रहता है जब जीसीसी अनुकूलन चालू है की तुलना में थोड़ा तेज है । Valgrind अभी भी बहुत सारे कैश लिखने की यादें रिपोर्ट करता है।
तो, प्रश्न हैं: क्यों वे (एल 1 + एलएल) कैश याद करते हैं, भले ही मैं एनटीए स्ट्रीमिंग निर्देशों का उपयोग कर रहा हूं? विशेष रूप से func4 इतनी धीमी क्यों है ?! क्या कोई यहां बता रहा है कि क्या हो रहा है?
यदि आप ऑप्टिमाइज़ेशन सक्षम के साथ संकलित कर रहे हैं तो आपको वास्तव में यह जानने के लिए असेंबली को देखना होगा कि क्या हो रहा है। – RussS
मैं असेंबली को देख रहा हूं, जो प्रत्येक अनुकूलन स्तर के साथ पढ़ने के लिए बीटीडब्ल्यू को और अधिक कठिन हो जाता है, लेकिन यह मुझे नहीं बताता कि गैर-अस्थायी संकेत क्यों अनदेखा किया जाता है। कम से कम मुझे लगता है कि इसे अनदेखा किया जाता है क्योंकि वाल्ग्रिंड अभी भी कैश मिस की रिपोर्ट करता है जहां मुझे कोई उम्मीद नहीं है। वैसे भी, मुझे पता है कि सवाल बल्कि अनपेक्षित है, इसलिए मैं यहां क्या हो सकता है इस पर किसी भी इनपुट की सराहना करता हूं। –