你认为是什么让这个C++代码变慢了?(它循环通过ADODB记录集,将COM类型转换为字符串,并填充ostringstre

What do you think is making this C++ code slow? (It loops through an ADODB recordset, converts COM types to strings, and fills an ostringstream)

本文关键字:记录 ADODB COM 填充 ostringstre 字符串 类型转换 循环 是什么 C++ 代码      更新时间:2023-10-16

这个循环比我预期的要慢,我还不确定在哪里。看到什么了吗?

我正在阅读一个Accces数据库,使用客户端游标。当我有127000行和20列时,这个循环大约需要10秒。这20列是字符串、int和日期类型。所有类型在放入ostringstream缓冲区之前都会转换为ANSI字符串。

void LoadRecordsetIntoStream(_RecordsetPtr& pRs, ostringstream& ostrm)
{
    ADODB::FieldsPtr pFields = pRs->Fields;
    char buf[80];
    ::SYSTEMTIME sysTime;
    _variant_t var;
    while(!pRs->EndOfFile) // loop through rows
    {
        for (long i = 0L; i < nColumns; i++)  // loop through columns
        {
            var = pFields->GetItem(i)->GetValue();
            if (V_VT(&var) == VT_BSTR)
            {
                ostrm << (const char*) (_bstr_t) var;   
            }
            else if (V_VT(&var) == VT_I4
            || V_VT(&var) == VT_UI1
            || V_VT(&var) == VT_I2
            || V_VT(&var) == VT_BOOL)
            {
                ostrm << itoa(((int)var),buf,10);
            }
            else if (V_VT(&var) == VT_DATE)
            {
                ::VariantTimeToSystemTime(var,&sysTime);
                _stprintf(buf, _T("%4d-%02d-%02d %02d:%02d:%02d"),
                sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
                sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
                ostrm << buf;
            }
        }
        pRs->MoveNext();
    }
}

编辑:经过更多的实验。。。

我现在知道,大约一半的时间被这句话占用了:
var=pFields->GetItem(i)->GetValue();

如果我绕过Microsoft生成的COM包装,我的代码会更快吗?我的猜测是否定的。

另一半的时间花在转换数据并将其流式传输到ostringstream的语句上。

我现在写这篇文章的时候不知道是转换还是流媒体需要更多的时间。

如果我不使用ostringstream,而是管理我自己的缓冲区,用我自己的逻辑来增加缓冲区(重新分配、复制、删除),会更快吗?如果我的逻辑做出悲观的猜测,并在前面为ostringstream缓冲区保留大量空间,会更快吗?这些可能是值得尝试的实验。

最后,转换本身。在我的时间安排上,这三个人没有一个表现得很糟糕。一个答案是,我的itoa可能比其他选择慢。值得一看。

我看不出你的代码,更熟悉COM/ATL的人可能会有更好的答案。

通过试错,我会通过注释掉内部循环操作来找到慢代码,直到你看到perf-speak,然后你就有了罪魁祸首,应该专注于此。

我假设V_VT是一个函数——如果是这样,那么对于每个日期值,V_VT(&var)被调用6次。一个简单的优化是本地存储V_VT(&var)的值以保存,每次循环最多保存5次对此函数的调用。

如果您还没有这样做,请重新排序类型的If测试,将最常见的列类型放在第一位,这样可以减少所需的测试数量。

尝试注释掉for循环中的代码并比较时间。一旦你读到了,就开始取消各个部分的注释,直到你碰到瓶颈。

Access不是一个服务器数据库,所有的文件读/写、锁定、光标处理等都在客户端应用程序中进行(通过网络,对吧?)如果其他用户同时打开数据库,则需要这样做。

如果没有,您可能可以删除光标设置,然后以只读方式打开数据库。

作为一个基本想法,当您只有VT_BSTR转换时,您应该尝试查看代码的速度,然后使用VT_DATE,最后使用其他类型,看看哪种类型花费的时间最多。

我唯一的观察是,itoa不是标准的C。正如您从本文中看到的,实现可能非常缓慢。

尝试分析。如果你没有探查器,一个简单的方法可以是将所有调用包装在你的循环中,你认为这可能需要一些时间,如下所示:

#define TIME_CALL(x) 
do { 
  const DWORD t1 = timeGetTime();
  x;
  const DWORD t2 = timeGetTime();
  std::cout << "Call to '" << #x << "' took " << (t2 - t1) << " ms.n";
}while(false)

所以现在你可以说:

TIME_CALL(var = pFields->GetItem(i)->GetValue());
TIME_CALL(ostrm << (const char*) (_bstr_t) var);

等等…

您不需要itoa-您正在向流写入。

要回答您的新问题,我认为您应该使用这样一个事实,即您可以让流格式化您的数据,而不是将其格式化为字符串,然后将该字符串传递给流,例如:

_stprintf(buf, _T("%4d-%02d-%02d %02d:%02d:%02d"),
                  sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
                  sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
ostrm << buf;

转化为:

ostrm.fill('0');
ostrm.width(4);
ostrm << sysTime.wYear << _T("-");
ostrm.width(2);
ostrm << sysTime.wMonth;

等等…