Нужен быстрый метод для преобразования большого количества двойных в строку - PullRequest
0 голосов
/ 28 декабря 2018

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

Мой план:

  1. Моя задача - вставить результаты в базу данных (PostgreSQL) с относительнойбыстрая скорость.
  2. Я использую [COPY FROM STDIN] libpq, который мне сказали как самый быстрый способ.
  3. Метод требует преобразования результатов в формат char *.

Хотя результаты выглядят так:

  1. Ежемесячный денежный поток в течение следующих 106 лет (всего 1272 в два раза).
  2. Около 14 денежных потоков на каждого участника.
  3. Около 2800 объектов (2790 для проверенных данных).

И таблица в базе данных выглядит следующим образом:

  1. Каждая строка таблицы содержит один объект.
  2. Существуют некоторые префиксы для идентификации различных сущностей.
  3. CashFlow - двойной массив, следующий за префиксом (тип float8 [] в PGSQL).

Ниже представлен коддля создания таблицы в базе данных:

create table AgentCF(
PlanID     int4,
Agent      int4,
Senario    int4,
RM_Prev    float8[], DrvFac_Cur float8[], Prem       float8[],
Comm       float8[], CommOR     float8[], FixExp     float8[],
VarExp     float8[], CIRCFee    float8[], SaftyFund  float8[],
Surr       float8[], Benefit_1  float8[], Benefit_2  float8[],
Benefit_3  float8[], Benefit_4  float8[], Benefit_5  float8[],
Benefit_6  float8[], Benefit_7  float8[], Benefit_8  float8[],
Benefit_9  float8[], Benefit_10 float8[]
);

Представление cода для функции, которая готовит вставленный CashFlow:

void AsmbCF(char *buffer, int size, int ProdNo, int i, int Pos, int LineEnd)
{
    int     j, Step = sizeof(nodecf) / sizeof(double), PosST, Temp;
    double *LoopRate = &AllHeap[ProdNo].Heap.AgentRes[i].CF.NodeCF[0].Prem;
    strcpy_s(buffer, size, "{");
    for (j = 0; j < TOTLEN / 10; j++) {
        PosST = j * 10 * Step + Pos;
        sprintf_s(&buffer[strlen(buffer)], size - strlen(buffer), "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,",
            LoopRate[PosST],
            LoopRate[PosST + 1 * Step],
            LoopRate[PosST + 2 * Step],
            LoopRate[PosST + 3 * Step],
            LoopRate[PosST + 4 * Step],
            LoopRate[PosST + 5 * Step],
            LoopRate[PosST + 6 * Step],
            LoopRate[PosST + 7 * Step],
            LoopRate[PosST + 8 * Step],
            LoopRate[PosST + 9 * Step]
        );
    }
    Temp = j * 10;
    PosST = Temp * Step + Pos;
    sprintf_s(&buffer[strlen(buffer)], size - strlen(buffer), "%f", LoopRate[PosST]);
    Temp = Temp + 1;
    for (j = Temp; j < TOTLEN; j++) {
        PosST = j * Step + Pos;
        sprintf_s(&buffer[strlen(buffer)], size - strlen(buffer), ",%f", LoopRate[PosST]);
    }
    if (LineEnd) {
        strcat_s(buffer, size, "}\n");
    }
    else {
        strcat_s(buffer, size, "}\t");
    }
}

Ниже приведен код для скоростного тестирования:

void ThreadOutP(LPVOID pM)
{
    char       *buffer = malloc(BUFFLEN), sql[SQLLEN];
    int         Status, ProdNo = (int)pM, i, j, ben;
    PGconn     *conn = NULL;
    PGresult   *res;
    clock_t     begin, end;

    fprintf_s(fpOutP, "PlanID %d Start inseting...\n", AllHeap[ProdNo].PlanID);
    begin = clock();
    DBConn(&conn, CONNSTR, fpOutP);

#pragma region General cashflow
    //============================== Data Query ==============================
    //strcpy_s(&sql[0], SQLLEN, "COPY AgentCF(PlanID,Agent,Senario,Prem,Comm,CommOR,CIRCFee,SaftyFund,FixExp,VarExp,Surr");
    //for (ben = 1; ben <= AllHeap[ProdNo].Heap.TotNo.NoBenft; ben++) {
    //  strcat_s(&sql[0], SQLLEN, ",Benefit_");
    //  _itoa_s(ben, &sql[strlen(sql)], sizeof(sql) - strlen(sql), 10);
    //}
    //strcat_s(&sql[0], SQLLEN, ") FROM STDIN;");
    //res = PQexec(conn, &sql[0]);
    //if (PQresultStatus(res) != PGRES_COPY_IN) {
    //  fprintf_s(fpOutP, "Not in COPY_IN mode\n");
    //}
    //PQclear(res);
    //============================== Data Apply ==============================
    for (i = 0; i < AllHeap[ProdNo].MaxAgntPos + AllHeap[ProdNo].Heap.TotNo.NoSensi; i++) {
        sprintf_s(buffer, BUFFLEN, "%d\t%d\t%d\t", AllHeap[ProdNo].PlanID, AllHeap[ProdNo].Heap.AgentRes[i].Agent, AllHeap[ProdNo].Heap.AgentRes[i].Sensi);
        //Status = PQputCopyData(conn, buffer, (int)strlen(buffer));
        //if (1 != Status) {
        //  fprintf_s(fpOutP, "PlanID %d inserting error for agent %d\n", AllHeap[ProdNo].PlanID, AllHeap[ProdNo].Heap.AgentRes[i].Agent);
        //}
        for (j = 0; j < 8 + AllHeap[ProdNo].Heap.TotNo.NoBenft; j++) {
            if (j == 7 + AllHeap[ProdNo].Heap.TotNo.NoBenft) {
                AsmbCF(buffer, BUFFLEN, ProdNo, i, j, 1);
            }
            else {
                AsmbCF(buffer, BUFFLEN, ProdNo, i, j, 0);
            }
            //Status = PQputCopyData(conn, buffer, (int)strlen(buffer));
            //if (1 != Status) {
            //  fprintf_s(fpOutP, "PlanID %d inserting error for agent %d\n", AllHeap[ProdNo].PlanID, AllHeap[ProdNo].Heap.AgentRes[i].Agent);
            //}
        }
    }
    //Status = PQputCopyEnd(conn, NULL);
#pragma endregion

#pragma region K cashflow

#pragma endregion

    PQfinish(conn);
    FreeProd(ProdNo);
    free(buffer);
    end = clock();
    fprintf_s(fpOutP, "PlanID %d inserted, total %d rows inserted, %d millisecond cost\n", AllHeap[ProdNo].PlanID, i, end - begin);
    AllHeap[ProdNo].Printed = 1;
}

Обратите внимание, что я отключаю код, который включает вставку.

Результаты тестирования:

  1. Стоимость только сборки строки составляет 45930 миллисекунд.
  2. Стоимость сборки строки и вставки составляет 54829 миллисекунд.

Таким образом, большая часть затрат заключается в преобразовании double в char.

Поэтому я хотел бы спросить, существует ли более быстрый способ преобразования серии double в строку, потому что по сравнению с затратами на вычислениеУзким местом является вывод результатов.

Кстати, моя платформа - Windows 10, PostgreSQL 11, Visual Studio 2017.

Большое спасибо!

Ответы [ 4 ]

0 голосов
/ 28 декабря 2018

Я провел некоторую бухгалтерию по исходному коду:


  Total score("function" calls):
    2 + 4*TOTLEN * strlen()
    1 + 2*TOTLEN * sprintf() 
    1 * strcat()

  Estimated string() cost:
    3 + 4* size * (TOTLEN*TOTLEN) / 2 (measured in characters)

  Estimated sprintf() cost:
    2 * TOTLEN (measured in %lf conversions)
    2 * size (measured in characters)

Теперь я не знаю, что такое TOTLEN, но вызывает strlen () и друзей для постоянно растущей строкиприводит к квадратичному поведению, см. https://en.wikipedia.org/wiki/Joel_Spolsky#Schlemiel_the_Painter.27s_algorithm


  • профиль / меру (или подумайте), прежде чем оптимизировать
  • snprintf(), при правильном использовании безопасен при переполнении;прочитайте страницу руководства и используйте возвращаемое значение
  • , функции strxxx_x() практически бесполезны, они существуют только для того, чтобы угодить PHBs
0 голосов
/ 28 декабря 2018

На самом деле есть несколько более быстрых методов для точного представления чисел с плавающей запятой в виде строк, один из них - Грису, Флориан Лойч .

Этот репозиторий Github сравниваетнесколько алгоритмов на C и C ++, и он содержит исходный код для метода Grisu2 на C , который, по его утверждению, в 5,7 раза быстрее, чем sprintf.

Однако автортот же репозиторий ( Milo Yip ) предоставляет собственную реализацию C ++ с одним заголовком, которая, как утверждается, работает в 9,1 раза быстрее, предположительно, поскольку большинство функций полностью встроены.Я считаю, что перенос этого кода на C должен быть тривиальным, поскольку он не использует какой-либо специальный синтаксис C ++.

0 голосов
/ 28 декабря 2018

В качестве альтернативы ответу chux, я сделал следующую функцию:

__inline char* dbltoa(char* buff, double A, int Precision)
{
    int     Temp;
    char   *ptr;

    Temp = (int)A;
    _itoa_s(Temp, buff, 50, 10);
    ptr = buff + strlen(buff);
    ptr[0] = '.';
    Temp = (int)((A - Temp) * pow(10, Precision));
    _itoa_s(Temp, ptr + 1, 50, 10);
    return ptr + strlen(ptr);
}

И обновил функцию, которая создает строку CashFlow:

void AsmbCF(char *buffer, int size, int ProdNo, int i, int Pos, int LineEnd)
{
    int     j, Step = sizeof(nodecf) / sizeof(double), PosST, Temp;
    double *LoopRate = &AllHeap[ProdNo].Heap.AgentRes[i].CF.NodeCF[0].Prem;
    char   *ptr;
    strcpy_s(buffer, size, "{");
    ptr = buffer + 1;
    for (j = 0; j < TOTLEN; j++) {
        PosST = j * Step + Pos;
        ptr = dbltoa(ptr, LoopRate[PosST], 8);
        ptr[0] = ',';
        ptr++;
    }
    ptr[-1] = 0;
    if (LineEnd) {
        strcat_s(buffer, size, "}\n");
    }
    else {
        strcat_s(buffer, size, "}\t");
    }
}

Результат теста без вставки составляет 4558 миллисекундв то время как со встроенными тэками 29260 миллисекунд (вероятно, параллельный запуск базы данных сделал это неэквивалентным по соотношению).

0 голосов
/ 28 декабря 2018

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

Для применения в полном диапазоне double используйте sprintf(buf, "%a", some_double).Если требуется десятичный вывод, используйте "%e".

. Любой другой код будет работать быстрее, только если он содержит точность или допустимый диапазон ввода каким-либо образом .

Обычный подход заключается в преобразовании double x в некоторое масштабированное целое число и преобразовании его в строку.Это подразумевает ограничения на x, которые еще явно не выражены в OP.

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


ОП должен опубликовать код проверки скорости для объективной оценки производительности.

...