0

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

我正在使用客户端游标读取 Accces DB。当我有 20 列的 127,000 行时,这个循环大约需要 10 秒。20 列是字符串、整数和日期类型。所有类型在放入 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 可能比另一种慢。值得一试。

4

8 回答 8

2

我无法从您的代码中看出,更熟悉 COM/ATL 的人可能会有更好的答案。

通过试验 n 错误,我会通过注释掉内部循环操作来找到慢代码,直到你看到性能峰值,那么你就有了罪魁祸首,应该专注于这一点。

于 2008-11-07T04:18:18.967 回答
2

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

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

于 2008-11-07T04:20:36.997 回答
1

一个很好的部分是 Access 不是服务器数据库 - 所有文件读/写、锁定、游标处理等都发生在客户端应用程序中(通过网络,对吗?)并且需要这样做,如果其他用户同时打开了数据库。

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

于 2008-11-07T04:29:26.853 回答
1

尝试注释掉for循环中的代码并比较时间。阅读完毕后,开始取消注释各个部分,直到遇到瓶颈。

于 2008-11-07T07:30:59.110 回答
1

作为一个基本概念,您应该尝试在只有 VT_BSTR 转换时查看代码的速度,然后是 VT_DATE,最后是其他类型,看看哪个花费的时间最多。

我唯一的观察是 itoa 不是标准的 C。从本文中可以看出,实现可能非常慢。

于 2008-11-07T09:51:52.337 回答
0

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

#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);

等等...

于 2008-11-07T07:16:04.397 回答
0

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

于 2008-11-07T10:17:38.447 回答
0

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

_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;

等等...

于 2008-11-07T15:51:36.823 回答