Как вы думаете, что делает этот код C ++ медленным? (Он перебирает набор записей ADODB, преобразует типы COM в строки и заполняет поток ostring) - PullRequest
0 голосов
/ 07 ноября 2008

Этот цикл медленнее, чем я ожидал, и я пока не уверен, где. Видите что-нибудь?

Я читаю базу данных Accces, используя клиентские курсоры. Когда у меня 127 000 строк с 20 столбцами, этот цикл занимает около 10 секунд. 20 столбцов являются строковыми, целыми и типами даты. Все типы преобразуются в строки ANSI, прежде чем они помещаются в буфер ostringstream.

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

Если я обойду созданные COM-оболочки Microsoft, будет ли мой код быстрее? Я думаю, нет.

Другая половина времени тратится на операторы, которые преобразуют данные и направляют их в поток ostring.

Я не знаю сейчас, когда я пишу это, будь то преобразования или потоковая передача, которые занимают больше времени.

Было бы быстрее, если бы я не использовал ostringstream и вместо этого управлял своим собственным буфером со своей собственной логикой для увеличения буфера (перераспределение, копирование, удаление)? Было бы быстрее, если бы моя логика сделала пессимистическое предположение и зарезервировала много места для буфера ostringstream заранее? Это могут быть эксперименты, которые стоит попробовать.

Наконец, сами преобразования. Ни один из трех не выделяется в моей жизни как плохой. Один ответ говорит, что моя Итоа может быть медленнее, чем альтернатива. Стоит проверить.

Ответы [ 8 ]

2 голосов
/ 07 ноября 2008

Я предполагаю, что V_VT является функцией - если так, то для каждого значения даты V_VT (& var) вызывается 6 раз. Простая оптимизация заключается в локальном сохранении значения V_VT (& var), чтобы сохранить до 5 вызовов этой функции каждый раз в цикле.

Если вы еще этого не сделали, измените порядок тестов if для типов, чтобы поставить наиболее распространенные типы столбцов на первое место - это уменьшит количество требуемых тестов.

2 голосов
/ 07 ноября 2008

Я не могу судить по вашему коду, кто-то, более знакомый с COM / ATL, может найти лучший ответ.

По ошибке n теста я бы нашел медленный код, закомментировав операции внутреннего цикла, пока вы не увидите всплеск перфекта, тогда у вас будет виновник, и вы должны сосредоточиться на этом.

1 голос
/ 07 ноября 2008

В качестве базовой идеи вы должны попытаться увидеть скорость кода, когда у вас есть только преобразование VT_BSTR, после этого с VT_DATE и, наконец, с другими типами, посмотрите, какое из них занимает больше времени.

Единственное замечание, которое у меня есть, это то, что itoa не является стандартным C. Реализация может быть очень медленной, как вы можете видеть из этой статьи.

1 голос
/ 07 ноября 2008

Попробуйте закомментировать код в цикле for и сравнить время. После прочтения начните раскомментировать различные разделы, пока не дойдете до горлышка бутылки.

1 голос
/ 07 ноября 2008

Хорошей частью этого является то, что Access не является базой данных сервера - все чтение / запись файла, блокировка, обработка курсора и т. Д. Происходит в клиентском приложении (по сети, верно?) поэтому, если у других пользователей одновременно открыта база данных.

Если нет, вы, вероятно, сможете сбросить настройки курсора и открыть базу данных только для чтения.

0 голосов
/ 07 ноября 2008

Чтобы ответить на ваш новый вопрос, я думаю, вы должны использовать тот факт, что вы можете позволить потоку форматировать ваши данные вместо форматирования их в строку, а затем передавать эту строку в поток, например:

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

И так далее ...

0 голосов
/ 07 ноября 2008

Вам не нужна itoa - вы пишете в поток.

0 голосов
/ 07 ноября 2008

Попробуйте профилирование. Если у вас нет профилировщика, простым способом может быть обернуть все вызовы в вашем цикле, как вы думаете, это может занять некоторое время, например:

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

и так далее ...

...