Ardışık dizilerde akış performansını görmek için AVX -AVX2 komut kümeleriyle denemeler yapıyordum. Bu yüzden temel hafızayı okuduğum ve sakladığım bir örneğim var. Haswell bellek erişimi
#include <iostream>
#include <string.h>
#include <immintrin.h>
#include <chrono>
const uint64_t BENCHMARK_SIZE = 5000;
typedef struct alignas(32) data_t {
double a[BENCHMARK_SIZE];
double c[BENCHMARK_SIZE];
alignas(32) double b[BENCHMARK_SIZE];
}
data;
int main() {
data myData;
memset(&myData, 0, sizeof(data_t));
auto start = std::chrono::high_resolution_clock::now();
for (auto i = 0; i < std::micro::den; i++) {
for (uint64_t i = 0; i < BENCHMARK_SIZE; i += 1) {
myData.b[i] = myData.a[i] + 1;
}
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << (end - start).count()/std::micro::den << " " << myData.b[1]
<< std::endl;
}
ve ile derleme sonra
g ++ - 4.9 -ggdb -march = çekirdek AVX2 -std = C++ 11 struct_of_arrays.cpp -O3 -o struct_of_arraysI döngüsü performansına göre oldukça iyi bir talimat bakınız ve zamanlama, benek boyutu 4000 için. Ancak, benchmark büyüklüğünü 5000'e yükselttiğimde, devir başına talimatın önemli ölçüde düştüğünü ve ayrıca gecikme atladığını görüyorum. Şimdi sorum şu ki, bu performans düşüşünü L1 önbelleği ile ilgili görebiliyor gibi görsem de, bunun neden bu kadar aniden olduğunu açıklayamıyorum. Ben Benchmark büyüklüğü 4000 ile perf çalıştırırsanız
, daha fazla fikir vermek için, ve bu darbe neden oluyor 5000| Event | Size=4000 | Size=5000 |
|-------------------------------------+-----------+-----------|
| Time | 245 ns | 950 ns |
| L1 load hit | 525881 | 527210 |
| L1 Load miss | 16689 | 21331 |
| L1D writebacks that access L2 cache | 1172328 | 623710387 |
| L1D Data line replacements | 1423213 | 624753092 |
Benim soru, Haswell dikkate 2 * 32 bayt sunma yeteneğine olmalıdır edilir Okumak ve 32 bayt her döngü saklamak?
Bu kod gcc ile gerçekleştirilen 1
DÜZENLEME akıllıca ortadan kaldırır o Bunu önlemek için 0'a ayarlanır beri myData.a için erişir yaptım, bir açık ayarlanır nerede biraz farklı olan başka kriter .
#include <iostream>
#include <string.h>
#include <immintrin.h>
#include <chrono>
const uint64_t BENCHMARK_SIZE = 4000;
typedef struct alignas(64) data_t {
double a[BENCHMARK_SIZE];
alignas(32) double c[BENCHMARK_SIZE];
alignas(32) double b[BENCHMARK_SIZE];
}
data;
int main() {
data myData;
memset(&myData, 0, sizeof(data_t));
std::cout << sizeof(data) << std::endl;
std::cout << sizeof(myData.a) << " cache lines " << sizeof(myData.a)/64
<< std::endl;
for (uint64_t i = 0; i < BENCHMARK_SIZE; i += 1) {
myData.b[i] = 0;
myData.a[i] = 1;
myData.c[i] = 2;
}
auto start = std::chrono::high_resolution_clock::now();
for (auto i = 0; i < std::micro::den; i++) {
for (uint64_t i = 0; i < BENCHMARK_SIZE; i += 1) {
myData.b[i] = myData.a[i] + 1;
}
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << (end - start).count()/std::micro::den << " " << myData.b[1]
<< std::endl;
}
İkinci örnekte bir dizi okunacak ve diğer dizi yazılacak. ve bu farklı boyutları için perf çıkış aşağıdaki üretir: artık L1 sığmayan veri kümesi boyutu artan veriler ve L2 darboğaz ile cevap belirttiği gibi
| Event | Size=1000 | Size=2000 | Size=3000 | Size=4000 |
|----------------+-------------+-------------+-------------+---------------|
| Time | 86 ns | 166 ns | 734 ns | 931 ns |
| L1 load hit | 252,807,410 | 494,765,803 | 9,335,692 | 9,878,121 |
| L1 load miss | 24,931 | 585,891 | 370,834,983 | 495,678,895 |
| L2 load hit | 16,274 | 361,196 | 371,128,643 | 495,554,002 |
| L2 load miss | 9,589 | 11,586 | 18,240 | 40,147 |
| L1D wb acc. L2 | 9,121 | 771,073 | 374,957,848 | 500,066,160 |
| L1D repl. | 19,335 | 1,834,100 | 751,189,826 | 1,000,053,544 |
Yine aynı model görülür. nedir, aynı zamanda, prefetching'in yardım etmeyeceği ve L1'in kaçırdığı 'un önemli ölçüde arttığı da ilginçtir. Her ne kadar okuma için L1'e getirilen her önbellek çizgisi dikkate alındığında en az yüzde 50 isabet oranı görmeyi beklerdim, ikinci erişim için bir isabetli olacaktır (her bir yineleme ile 64 bayt önbellek satırı 32 bayt okunur). Ancak, bir kez veri seti L2'ye döküldüğünde, L1 vuruş oranı% 2'ye düşüyor. Diziler göz önüne alındığında, L1 önbellek boyutu ile gerçekten örtüşmezler, bu önbellek çakışmalarından kaynaklanmamalıdır. Yani bu kısım hala bana mantıklı gelmiyor.
+1. Ekleyeceğim tek şey, gördüğüm her x86 platformunda, bir çiftin 8 bayt olmasıdır. –
Gerçekten de, arka planları ve L1'de olmadıklarında bant genişliğini nasıl kullandıklarını bilmek hakkınız vardır. Veriler L1'de değilse (L1'den daha büyük herhangi bir gerçek zamanlı kullanım durumunda hemen hemen her zaman olacaksa) işlem ünitesinin gücünü kullanamamak hayal kırıklığı yaratıyor. – edorado
Bu nedenle, performans kritik algoritmaları çalışma kümelerini genellikle daha küçük önbelleklere sığabilen alt kümelere ayırır (örneğin, önbellek döşeme tekniklerine bakın). Makaleye göre L2 bant genişliği eski CPU'lara göre de artmıştı, sanırım L1 geliştirmeleri ile yetinmek zor oluyor. – Leeor