2016-02-25 5 views
9

से बेहतर काम करता है मैं विभिन्न डेटा संरचनाओं और तकनीकों (वैक्टर, सरणी और ओपनएमपी) के साथ मैट्रिस के लिए सी ++ गुणा लागू कर रहा हूं और मुझे एक अजीब स्थिति मिली ... मेरी गतिशीलता सरणी संस्करण बेहतर काम कर रहा है:गतिशील सरणी के साथ सी ++ गुणा क्यों std :: वेक्टर संस्करण

बार:

OpenMP mult_1: समय: ५.८८२००० रों

सरणी mult_2: समय: १.४७८००० रों

मेरे संकलन झंडे हैं:

/usr/bin/जी ++ -fopenmp -pthread -std = C++ 1 वर्ष -O3

सी ++ वेक्टर संस्करण

typedef std::vector<std::vector<float>> matrix_f; 
void mult_1 (const matrix_f & matrixOne, const matrix_f & matrixTwo, matrix_f & result) { 
    const int matrixSize = (int)result.size(); 
    #pragma omp parallel for simd 
    for (int rowResult = 0; rowResult < matrixSize; ++rowResult) { 
     for (int colResult = 0; colResult < matrixSize; ++colResult) { 
      for (int k = 0; k < matrixSize; ++k) { 
       result[rowResult][colResult] += matrixOne[rowResult][k] * matrixTwo[k][colResult]; 
      } 
     } 
    } 
} 

गतिशील सरणी संस्करण

void mult_2 (float * matrixOne, float * matrixTwo, float * result, int size) { 
    for (int row = 0; row < size; ++row) { 
     for (int col = 0; col < size; ++col) { 
      for (int k = 0; k < size; ++k) { 
       (*(result+(size*row)+col)) += (*(matrixOne+(size*row)+k)) * (*(matrixTwo+(size*k)+col)); 
      } 
     } 
    } 
} 
,210

परीक्षण:

सी ++ वेक्टर संस्करण

utils::ChronoTimer timer; 
/* set Up simple matrix */ 
utils::matrix::matrix_f matr1 = std::vector<std::vector<float>>(size,std::vector<float>(size)); 
fillRandomMatrix(matr1); 

utils::matrix::matrix_f matr2 = std::vector<std::vector<float>>(size,std::vector<float>(size)); 
fillRandomMatrix(matr2); 

utils::matrix::matrix_f result = std::vector<std::vector<float>>(size,std::vector<float>(size));  
timer.init(); 
utils::matrix::mult_1(matr1,matr2,result); 
std::printf("openmp mult_1: time: %f ms\n",timer.now()/1000); 

गतिशील सरणी संस्करण

utils::ChronoTimer timer; 

float *p_matr1 = new float[size*size]; 
float *p_matr2 = new float[size*size]; 
float *p_result = new float[size*size]; 

fillRandomMatrixArray(p_matr1,size); 
fillRandomMatrixArray(p_matr2,size); 

timer.init(); 
utils::matrix::mult_2(p_matr1,p_matr2,p_result,size); 
std::printf("array mult_2: time: %f ms\n",timer.now()/1000); 

delete [] p_matr1; 
delete [] p_matr2; 
delete [] p_result; 

मैं कुछ पिछले पोस्ट की जाँच की गई थी, लेकिन मैं किसी के साथ संबंधित नहीं पा सके मेरी समस्या link, link2, link3:

अद्यतन: मैं जवाब के साथ परीक्षण refactorized, और वेक्टर बेहतर slighty काम करता है:

वेक्टर mult: समय: १.१९४००० रों

सरणी mult_2: समय: १.२०२००० रों

सी ++ वेक्टर संस्करण

void mult (const std::vector<float> & matrixOne, const std::vector<float> & matrixTwo, std::vector<float> & result, int size) { 
    for (int row = 0; row < size; ++row) { 
     for (int col = 0; col < size; ++col) { 
      for (int k = 0; k <size; ++k) { 
       result[(size*row)+col] += matrixOne[(size*row)+k] * matrixTwo[(size*k)+col]; 
      } 
     } 
    } 
} 

गतिशील सरणी संस्करण

void mult_2 (float * matrixOne, float * matrixTwo, float * result, int size) { 
    for (int row = 0; row < size; ++row) { 
     for (int col = 0; col < size; ++col) { 
      for (int k = 0; k < size; ++k) { 
       (*(result+(size*row)+col)) += (*(matrixOne+(size*row)+k)) * (*(matrixTwo+(size*k)+col)); 
      } 
     } 
    } 
} 

इसके अलावा, मेरी vectorized संस्करण बेहतर (0.803 रों) काम कर रहा है;

+7

डेटा स्मृति में अलग-अलग व्यवस्थित किया गया है। 'वेक्टर ' करते समय मैट्रिक्स स्मृति में संगत होते हैं, प्रत्येक वेक्टर को अलग-अलग आवंटित करते हैं। यदि आकार संकलित समय पर तय किया गया है तो आप 'वेक्टर <सर >' का प्रयास कर सकते हैं या यह सुनिश्चित करने के लिए कुछ और कर सकते हैं कि पूर्ण मैट्रिक्स स्मृति में सम्मिलित है। – PeterT

+0

देखें http://stackoverflow.com/questions/17259877/1d-or-2d-array-whats- आप आमतौर पर "असली" 2 डी संरचनाओं (जैसे 'टी **', 'वेक्टर > से बचना चाहते हैं) '...) घने matrices भंडारण के लिए। – Pixelchemist

+0

मुझे लगता है कि मेमोरी लेआउट आपका एकमात्र मुद्दा नहीं है। हमें अपना टाइमर कोड दिखाएं और आप कितने धागे ओपनएमपी संस्करण को चला रहे हैं। – jepio

उत्तर

12

वैक्टर का एक वेक्टर एक जंजीर सरणी के समान है - एक सरणी जहां प्रत्येक प्रविष्टि एक सूचक है, प्रत्येक पॉइंटर फ्लोट की दूसरी सरणी पर इंगित करता है।

तुलनात्मक रूप से, कच्चे सरणी संस्करण स्मृति का एक ब्लॉक है, जहां आप तत्वों को खोजने के लिए गणित करते हैं।

वैक्टर का वेक्टर नहीं, और गणित मैन्युअल रूप से एक वेक्टर का उपयोग करें। या, फिक्स-साइज्ड std::array एस के वेक्टर का उपयोग करें। या एक सहायक प्रकार लिखें जो फ्लोट के (एक-आयामी) वेक्टर लेता है, और आपको इसके 2 आयामी दृश्य देता है।

एक संगत बफर में डेटा डिस्कनेक्ट किए गए बफर के समूह में डेटा की तुलना में अधिक कैश और अनुकूलन अनुकूल है।

template<std::size_t Dim, class T> 
struct multi_dim_array_view_helper { 
    std::size_t const* dims; 
    T* t; 
    std::size_t stride() const { 
    return 
     multi_dim_array_view_helper<Dim-1, T>{dims+1, nullptr}.stride() 
     * *dims; 
    } 
    multi_dim_array_view_helper<Dim-1, T> operator[](std::size_t i)const{ 
    return {dims+1, t+i*stride()}; 
    } 
}; 
template<class T> 
struct multi_dim_array_view_helper<1, T> { 
    std::size_t stride() const{ return 1; } 
    T* t; 
    T& operator[](std::size_t i)const{ 
    return t[i]; 
    } 
    multi_dim_array_view_helper(std::size_t const*, T* p):t(p) {} 
}; 
template<std::size_t Dims> 
using dims_t = std::array<std::size_t, Dims-1>; 
template<std::size_t Dims, class T> 
struct multi_dim_array_view_storage 
{ 
    dims_t<Dims> storage; 
}; 
template<std::size_t Dims, class T> 
struct multi_dim_array_view: 
    multi_dim_array_view_storage<Dims, T>, 
    multi_dim_array_view_helper<Dims, T> 
{ 
    multi_dim_array_view(dims_t<Dims> d, T* t): 
    multi_dim_array_view_storage<Dims, T>{std::move(d)}, 
    multi_dim_array_view_helper<Dims, T>{ 
     this->storage.data(), t 
    } 
    {} 
}; 

अब आप यह कर सकते हैं:

std::vector<float> blah = { 
    01.f, 02.f, 03.f, 
    11.f, 12.f, 13.f, 
    21.f, 22.f, 23.f, 
}; 

multi_dim_array_view<2, float> view = { {3}, blah.data() }; 
for (std::size_t i = 0; i < 3; ++i) 
{ 
    std::cout << "["; 
    for (std::size_t j = 0; j < 3; ++j) 
    std::cout << view[i][j] << ","; 
    std::cout << "]\n"; 
} 

live example

कोई डेटा दृश्य कक्षा में कॉपी किया जाता है। यह केवल एक फ्लैट सरणी का एक दृश्य प्रदान करता है जो बहु-आयामी सरणी है।

2

आपका दृष्टिकोण काफी अलग हैं:

  • "गतिशील सरणी" संस्करण में आप प्रत्येक मैट्रिक्स के लिए स्मृति का एक भी हिस्सा आवंटित और कहा कि एक आयामी स्मृति सीमा पर मैट्रिक्स की पंक्तियों को मैप करें।

  • "वेक्टर" संस्करण में आप वेक्टरों के वेक्टर का उपयोग करते हैं जो "वास्तविक" और "गतिशील रूप से" दो आयामी अर्थ हैं कि आपके मैट्रिस की प्रत्येक पंक्ति की संग्रहण स्थिति अन्य पंक्तियों के संबंध में असंबंधित है।

क्या आप शायद क्या करना चाहते है:

  • उपयोग vector<float>(size*size) और आप "गतिशील सरणी" हाथ से उदाहरण या

  • लिखें में कर रहे हैं बहुत ही मानचित्रण प्रदर्शन एक वर्ग जो आंतरिक रूप से आपके लिए मैपिंग को संभालती है और 2-आयामी पहुंच इंटरफ़ेस प्रदान करती है (T& operator()(size_t, size_t) या किसी प्रकार का row_proxy operator[](size_t) जहां row_proxy बदले में T& operator[](size_t) है)

1

यह केवल संगत स्मृति के बारे में सिद्धांत (अभ्यास में) को लागू करने के लिए है।

जी ++ के साथ तैयार किए गए कोड पर कुछ विश्लेषण करने के बाद (-O2) स्रोत पाया जा सकता है पर: https://gist.github.com/42be237af8e3e2b1ca03

प्रासंगिक सरणी संस्करण के लिए तैयार किए गए कोड है:

.L3: 
    lea r9, [r13+0+rbx]    ; <-------- KEEPS THE ADDRESS 
    lea r11, [r12+rbx] 
    xor edx, edx 
.L7: 
    lea r8, [rsi+rdx] 
    movss xmm1, DWORD PTR [r9] 
    xor eax, eax 
.L6: 
    movss xmm0, DWORD PTR [r11+rax*4] 
    add rax, 1 
    mulss xmm0, DWORD PTR [r8] 
    add r8, r10 
    cmp ecx, eax 
    addss xmm1, xmm0 
    movss DWORD PTR [r9], xmm1  ; <------------ ADDRESS IS USED 
    jg .L6 
    add rdx, 4 
    add r9, 4      ; <--- ADDRESS INCREMENTED WITH SIZE OF FLOAT 
    cmp rdx, rdi 
    jne .L7 
    add ebp, 1 
    add rbx, r10 
    cmp ebp, ecx 
    jne .L3 

देखते हैं कि कैसे उपयोग r9 के मान का मूल्य गंतव्य सरणी के लिए संगत स्मृति और r8 इनपुट एरे में से एक के लिए प्रतिबिंबित कर रहा है।

दूसरे छोर पर, वैक्टर की वेक्टर उत्पन्न कोड की तरह:

.L12: 
    mov r9, QWORD PTR [r12+r11] 
    mov rdi, QWORD PTR [rbx+r11] 
    xor ecx, ecx 
.L16: 
    movss xmm1, DWORD PTR [rdi+rcx] 
    mov rdx, r10 
    xor eax, eax 
    jmp .L15 
.L13: 
    movaps xmm1, xmm0 
.L15: 
    mov rsi, QWORD PTR [rdx] 
    movss xmm0, DWORD PTR [r9+rax] 
    add rax, 4 
    add rdx, 24 
    cmp r8, rax 
    mulss xmm0, DWORD PTR [rsi+rcx] 
    addss xmm0, xmm1 
    movss DWORD PTR [rdi+rcx], xmm0 ; <------------ HERE 
    jne .L13 
    add rcx, 4 
    cmp rcx, r8 
    jne .L16 
    add r11, 24 
    cmp r11, rbp 
    jne .L12 

नहीं आश्चर्यजनक रूप से, संकलक चालाक पर्याप्त नहीं करने के लिए सभी operator [] कॉल के लिए कोड उत्पन्न करने के लिए है, और इनलाइनिंग का एक अच्छा काम करता है उन्हें, लेकिन देखें कि rdi + rcx के माध्यम से इसे अलग-अलग पतों को ट्रैक करने की आवश्यकता है, जब यह परिणाम वेक्टर पर मूल्य वापस संग्रहीत करता है, और अतिरिक्त उप-वैक्टर (mov rsi, QWORD PTR [rdx]) के लिए अतिरिक्त मेमोरी एक्सेस भी करता है जो सभी कुछ ओवरहेड उत्पन्न करते हैं।

संबंधित मुद्दे