【问题标题】:Run time issues using the Intel's Math Kernel Library for eigendecomposition使用英特尔的数学内核库进行特征分解的运行时问题
【发布时间】:2014-07-18 06:50:47
【问题描述】:

为了在 C++ 中进行特征分解,我使用例程“zhpev”。该例程嵌入在一个较大软件的 dll 文件中,并在运行时被彻底使用。在大约 5000 次“zhpev”调用之后,我测量了运行时间。前 900 次运行时评估一切正常。运行时间约为 0.7 秒,变化不大。然而,900 次运行时间评估,运行时间突然从 0.7 秒增加到 2.7 秒,变化很大。

我做了以下观察:

  • 运行时问题与“zhpev”的输入数据无关。
  • 例程“zhpev”在一个小程序中运行良好且稳定。似乎是与其他部分的交互造成了麻烦。
  • 将“zhpev”替换为另一个特征分解例程后,一切运行顺利,运行时间几乎没有变化。
  • 无论是否使用多线程,都会出现运行时问题。
  • 我不使用动态内存分配。所有变量都分配为静态变量。
  • 问题与Visual C++ function suddenly 170 ms slower (4x longer) 类似,但是,我无法在我的代码中检测到任何内存泄漏。

抱歉,由于我正在处理的项目太大,我无法发布任何代码。

如果有任何提示可以帮助我停止这种奇怪的行为,我将不胜感激!

编辑 例程“zhpev”适用于双精度大小为 32x32 的复杂 Hermitian 矩阵。因此,一次处理的数据块相当小。

更新 1)分页不是这里的问题。我在系统选项中禁用了页面文件。运行时间问题仍未解决。 2) 在不同的 Windows 计算机上运行应用程序也会导致相同的运行时间问题。但是,现在运行时间增加的开始发生在 1400 次运行时间评估之后。

更新 我发现只有在线程内调用“zhpev”时才会出现运行时问题。有了这个,我可以创建一个小代码示例,我会遇到同样的问题。

让我解释一下我的代码

这是我的代码

#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include "stdafx.h"
#include "mkl_lapack.h"
#include "mkl_service.h"
#include <time.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <iostream> 

using namespace std;
#define CACHE_LINE  32
#define CACHE_ALIGN __declspec(align(CACHE_LINE))

#define MAX_THREADS 2
#define BUF_SIZE 255

DWORD WINAPI MyThreadFunction( LPVOID lpParam );
void ErrorHandler(LPTSTR lpszFunction);

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// This is the critical function.
void Eigendecomposition();
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

typedef struct MyData {
    int val1;
    int val2;
} MYDATA, *PMYDATA;



int _tmain()
{
    PMYDATA pDataArray[MAX_THREADS];
    DWORD   dwThreadIdArray[MAX_THREADS];
    HANDLE  hThreadArray[MAX_THREADS]; 

    std::ofstream ofs;

    double tstart;
    double tend;

    double proc_time_pure;

    for(int j=0;j<10000;j++){

    // Start one iteration
    tstart = clock(); 



    // Create MAX_THREADS worker threads.

    for( int i=0; i<MAX_THREADS; i++ )
    {


        pDataArray[i] = (PMYDATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                sizeof(MYDATA));

        if( pDataArray[i] == NULL )
        {

            ExitProcess(2);
        }



        pDataArray[i]->val1 = i;
        pDataArray[i]->val2 = i+100;

        // Create the thread to begin execution on its own.

        hThreadArray[i] = CreateThread( 
            NULL,                   // default security attributes
            0,                      // use default stack size  
            MyThreadFunction,       // thread function name
            pDataArray[i],          // argument to thread function 
            0,                      // use default creation flags 
            &dwThreadIdArray[i]);   // returns the thread identifier 




        if (hThreadArray[i] == NULL) 
        {
           ErrorHandler(TEXT("CreateThread"));
           ExitProcess(3);
        }
    } // End of main thread creation loop.

    // Wait until all threads have terminated.

    WaitForMultipleObjects(MAX_THREADS, hThreadArray, TRUE, INFINITE);



    for(int i=0; i<MAX_THREADS; i++)
    {
        CloseHandle(hThreadArray[i]);
        if(pDataArray[i] != NULL)
        {
            HeapFree(GetProcessHeap(), 0, pDataArray[i]);
            pDataArray[i] = NULL;    // Ensure address is not reused.
        }
    }

    tend = clock();
    proc_time_pure = tend-tstart;

    // Print processing time into console and write it into a file
    printf("   Processing time: %4.3f \n", proc_time_pure/1000.0);
    ofs.open ("Processing_time.txt", std::ofstream::out | std::ofstream::app);

      ofs << proc_time_pure/1000.0 << " ";

      ofs.close();
    }
    return 0;
}


DWORD WINAPI MyThreadFunction( LPVOID lpParam ) 
{ 
    HANDLE hStdout;
    PMYDATA pDataArray;

    TCHAR msgBuf[BUF_SIZE];
    size_t cchStringSize;
    DWORD dwChars;



    hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    if( hStdout == INVALID_HANDLE_VALUE )
        return 1;



    pDataArray = (PMYDATA)lpParam;
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   // Critical function
    Eigendecomposition();
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    return 0; 
} 



void ErrorHandler(LPTSTR lpszFunction) 
{ 
    // Retrieve the system error message for the last-error code.

    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    // Display the error message.

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
        (lstrlen((LPCTSTR) lpMsgBuf) + lstrlen((LPCTSTR) lpszFunction) + 40) * sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, 
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"), 
        lpszFunction, dw, lpMsgBuf); 
    MessageBox(NULL, (LPCTSTR) lpDisplayBuf, TEXT("Error"), MB_OK); 

    // Free error-handling buffer allocations.

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
}

void Eigendecomposition(){
    const int M = 32;
    typedef MKL_Complex16  double_complex;
    const char    jobz = 'V';
    const char    uplo = 'L'; // lower triangular part of input matrix is used
    const MKL_INT dim = M;
    const MKL_INT ldz = M;
    const MKL_INT LWORK = (2*M-1);
    const MKL_INT LRWORK = (3*M-2);
    MKL_INT       info = 0;


    double_complex A_H_MKL[(M*M+M)/2];

    CACHE_ALIGN double_complex       work[LWORK]; 
    CACHE_ALIGN double               rwork[LRWORK];

    double D[M];
    double_complex U[M][M];
    for(int i=0;i<500;i++ ){
    // Create the input matrix
    for (int tmp=0; tmp < (M*M+M)/2; tmp++){
        A_H_MKL[tmp].real = 1  ;
        A_H_MKL[tmp].imag = 0;}

    // This is the mkl function
        zhpev(&jobz,                                // const char* jobz,
          &uplo,                                // const char* uplo,
          &dim,                                 // const MKL_INT* n,
          (double_complex *)&A_H_MKL[0],        // double_complex* ap,
          (double *)&D[0],                      // double* w,
          (double_complex *)&U[0][0],           // double_complex* z,
          &ldz,                                 // const MKL_INT* ldz,
          work,                                 // double_complex* work,
          rwork,                                // double* rwork,
          &info);                               // MKL_INT* info


}
}

【问题讨论】:

  • 资源泄漏(内存或其他)不一定在您的代码中,它实际上可能是库中的错误。
  • 谢谢,约阿希姆。但是,在没有任何大型环境的小测试函数中运行例程“zhpev”不会导致运行时间增加。因此,我认为问题的根源是“zhpev”和我的代码的一些问题的结合。
  • 那你有没有想过这个函数可能需要对某些参数集和/或数据进行更多的计算?或者您可能在较慢的呼叫中有更多数据?还是您每次都使用完全相同的参数和相同的数据大小调用函数?
  • 输入数据不是这里的问题。使用相同的输入调用函数会导致与不同的输入数据相同的行为。
  • 这是相当大的内存占用。碎片化怎么办? zphev 可能会尝试一次分配大量数据。如果没有连续的可用内存空间块,内存管理器可能会意外地提前打开分页...

标签: c++ visual-studio intel-mkl


【解决方案1】:

由于我缺乏细节,我只能尝试给出一个一般性的答案。我已经提到内存碎片是我的 cmets 中运行时间周期较长的一个可能原因。同样,缓存可能会成为软件执行过程中的瓶颈。您的应用程序的其他部分如何运行?他们是否也大量处理数据?

Stackoverflowers 已经在许多线程中讨论了缓存主题。例如阅读What is cache friendly code。我认为有些帖子信息量很大,也许可以帮助您理解您的问题。

【讨论】:

  • 谢谢诺曼纽斯。不幸的是,我不能使用您推荐的 ASM,因为我不允许在计算机上安装任何东西。这是我公司的政策。
  • 缓存问题不会影响软件执行的其他部分吗?我必须注意,该软件的所有其他部分都运行顺畅,没有任何困难。此外,软件的其他部分处理大量数据。
  • 这取决于您的其余代码是否也进行了缓存优化......在不了解 zhpev 的情况下,LAPACK 通常“旨在有效利用现代基于缓存的架构上的缓存”(参见 LAPACK 上的 Wiki)。因此,系统缓存上的流量可能会对(某些)LAPACK 算法的运行时特性产生影响。这只是一个猜测。您可以做什么: 1)在具有不同 CPU 缓存数量的不同系统上进行比较。 2)查看您的应用程序造成的缓存流量。缓存未命中率将是一个有趣的衡量标准。
【解决方案2】:

正如 normanius 已经说过的,如果没有实际看到您的代码和 zhpev 的实现,很难猜测可能是什么问题。但是,除了内存碎片和缓存问题之外,问题可能与 Hermitian 矩阵的属性有关。

从我所读到的关于 zhpev 的内容来看,毫无疑问它是基于一些迭代的数值分析方法。因此,根据所使用方法的属性,zhpev 收敛所需的时间(迭代次数)可能会因输入矩阵的属性而有很大差异。

其他矩阵属性的原因可能是 zhpev 手头有几种数值方法,它会根据输入矩阵分析选择最能利用其属性的一种方法来更快地计算它。

您说输入数据对性能问题没有影响,但是您能确定吗?您的 Hermitian 矩阵在计算过程中不会以某种方式演变吗?是否可以在每次相同的矩阵时获取 zhpev 以及实际的特征值计算并比较结果时间? (当然,你需要确保它不会被优化出来)

【讨论】:

  • 输入数据不是问题。请参阅我发布的代码示例。
【解决方案3】:

我在我的代码中发现了错误。我在使用 Windows 函数 CreatThread 创建的线程内运行特征分解例程。但是,没有函数可以结束线程,例如 WaitForMultipleObjects 例程。对于我的应用程序的所有其他部分,这不是问题,但特征分解遇到了困难。

【讨论】:

    猜你喜欢
    • 2017-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-03
    • 2011-10-02
    • 2013-01-09
    • 2022-01-01
    • 2015-12-08
    相关资源
    最近更新 更多