C ++ Perfomance Per Compiler, в 200 раз медленнее, чем C # - PullRequest
0 голосов
/ 24 октября 2019

Я имел дело с некоторыми проблемами производительности, которые я обсуждал в этом вопросе: Super Slow C ++ For Loop

У меня есть простая программа, которую я написал для анализа двоичных данных. Я тестировал его локально на 2 компьютерах.

1. Dual 6 core 2.4GHz Xeon V3, 64GB RAM, NVMe SSD
2. Dual 4 core 3.5GHz Xeon V3, 64GB RAM, NVMe SSD

Вот часть кода (остальное на Wandbox https://wandbox.org/permlink/VIvardJNAMKzSbMf):

string HexRow="";
for (int i=b; i<HexLineLength+b;i++){
    HexRow+= incomingData[i];
}

std::vector<unsigned char> BufferedLine=HexToBytes(HexRow);
stopwatch<> sw;
for (int i = 0; 80 >= i; ++i)
{
    Byte ColumnBytes;
    for (auto it = columns["data"][i].begin(); it != columns["data"][i].end(); ++it)
    {
        try {
            if (it.key() == "Column") { ColumnBytes.Column = it.value().get<std::string>();}
            else if (it.key() == "DataType") { ColumnBytes.DataType = it.value().get<std::string>();}
            else if (it.key() == "StartingPosition") { ColumnBytes.StartingPosition = it.value().get<int>();}
            else if (it.key() == "ColumnWidth") { ColumnBytes.ColumnWidth = it.value().get<int>();}
        }
        catch (...) {}
    }

    char* locale = setlocale(LC_ALL, "UTF-8");
    std::vector<unsigned char> CurrentColumnBytes(ColumnBytes.ColumnWidth);
    int arraySize = CurrentColumnBytes.size();

    for (int C = ColumnBytes.StartingPosition; C < ColumnBytes.ColumnWidth + ColumnBytes.StartingPosition; ++C)
    {
        int Index = C - ColumnBytes.StartingPosition;
        CurrentColumnBytes[Index] = BufferedLine[C-1];
    }
}
std::cout << "Elapsed: " << duration_cast<double>(sw.elapsed()) << '\n';

ПК 1

Компиляция наПК 1 с Visual Studio с использованием следующих флагов:

/O2 /JMC /permissive- /MP /GS /analyze- /W3 /Zc:wchar_t /ZI /Gm- /sdl /Zc:inline /fp:precise /D "_CRT_SECURE_NO_WARNINGS" /D "_MBCS" /errorReport:prompt /WX- /Zc:forScope /Gd /Oy- /MDd /std:c++17 /FC /Fa"Debug\" /EHsc /nologo /Fo"Debug\" /Fp"Debug\Project1.pch" /diagnostics:column

Вывод:

Elapsed: 0.0913771
Elapsed: 0.0419886
Elapsed: 0.042406

Использование Clang со следующими параметрами: clang main.cpp -O3 вывод:

Elapsed: 0.036262
Elapsed: 0.0174264
Elapsed: 0.0170038

Компиляция с GCC из MinGW gcc version 8.1.0 (i686-posix-dwarf-rev0, Built by MinGW-W64 project) с использованием этих переключателей gcc main.cpp -lstdc++ -O3 дает следующее время:

Elapsed: 0.019841
Elapsed: 0.0099643
Elapsed: 0.0094552

ПК 2

Я получаю с Visual Studio, все еще с / O2

Elapsed: 0.054841
Elapsed: 0.03543
Elapsed: 0.034552

Я не делал Clang и GCC на ПК 2, но улучшения не было достаточно значительным, чтобы решить мои проблемы.

Wandbox

Проблема в том, что точноетот же код на Wandbox (https://wandbox.org/permlink/VIvardJNAMKzSbMf) выполняется в 10-80 раз быстрее

Elapsed: 0.00115457
Elapsed: 0.000815412
Elapsed: 0.000814636

Wandbox использует GCC 10.0.0 и c ++ 14. Я понимаю, что он, вероятно, работает на Linux, и яНе удалось найти способ заставить GCC 10 компилироваться в Windows, поэтому я не могу проверить компиляцию с этой версией.

C # - 200X Faster

Этопереписать написанное мною приложение C #, которое работает намного быстрее:

Elapsed: 0.017424 
Elapsed: 0.0006065 
Elapsed: 0.000733 
Elapsed: 0.0006166 
Elapsed: 0.0004699 

Finished Parsing: 100 Records. Elapsed :0.0082796 at a rate of : 12076/s

Метод C # выглядит следующим образом:

Stopwatch sw = new Stopwatch();
sw.Start();
foreach (dynamic item in TableData.data)  //TableData is a JSON file with the structure definition
{

    string DataType = item.DataType;
    int startingPosition = item.StartingPosition;

    int width = Convert.ToInt32(item.ColumnWidth);
    if (width+startingPosition >= FullLineLength)
    {
        continue;
    }

    byte[] currentColumnBytes = currentLineBytes.Skip(startingPosition).Take(width).ToArray();

   // .....     200 extra lines of processing into ints, dates, strings       ......
   // ..... Even with the extra work, it operates at 1200+ records per second ......

}
sw.Stop();
var seconds = sw.Elapsed.TotalSeconds;
sw.Reset();
Console.WriteLine("Elapsed: " + seconds);
TempTable.Rows.Add(dataRow);

Когда я начал это, я ожидал огромного прироста производительностиперемещение кода в неуправляемый C ++ из C #. Это мой первый проект на C ++, и, честно говоря, я немного разочарован тем, где я нахожусь. Что можно сделать, чтобы ускорить этот C ++? Нужно ли использовать разные типы данных, malloc, больше / меньше структур?

Он должен работать в Windows, не уверен, есть ли способ заставить GCC 10 работать в Windows?

Какие у вас есть предложения для начинающего разработчика C ++?

Ответы [ 2 ]

0 голосов
/ 28 октября 2019

Хорошо, поэтому я смог заставить C ++ обрабатывать файл со скоростью около 50 000 строк в секунду с 80 столбцами в строке. Я переработал весь рабочий процесс, чтобы убедиться, что он вообще не должен возвращаться. Сначала я прочитал весь файл в ByteArray, а затем перебирал его построчно, перемещая данные из одного массива в другой, а не определяя каждый байт в цикле for. Затем я использовал map для хранения данных.

    stopwatch<> sw;
    while (CurrentLine < TotalLines)
    {
        int BufferOffset = CurrentLine * LineLength;
        std::move(ByteArray + BufferOffset, ByteArray + BufferOffset + LineLength, LineByteArray);
        for (int i = 0; TotalColumns > i + 1; ++i)
        {
            int ThisStartingPosition = StartingPosition[i];
            int ThisWidth = ColumnWidths[i];
            std::uint8_t* CurrentColumnBytes;
            CurrentColumnBytes = new uint8_t[ThisWidth];
            {
                std::move(LineByteArray + ThisStartingPosition, LineByteArray + ThisStartingPosition + ThisWidth, CurrentColumnBytes);
                ResultMap[CurrentLine][i] = Format(CurrentColumnBytes, ThisWidth, DataType[i]);
            }
        }
        CurrentLine++;
    }
    std::cout << "Processed" << CurrentLine << " lines in : " << duration_cast<double>(sw.elapsed()) << '\n';

Я все еще немного разочарован, потому что использование ускоренного григорианского календаря недоступно при использовании Clang для компиляции, а использование стандартного компилятора MS делает его почти в 20 раз медленнее. С Clang -O3 он обрабатывал 10 700 записей за 0,25 секунды, включая все преобразования int и string. Мне просто нужно написать свое собственное преобразование date.

0 голосов
/ 24 октября 2019

Это действительно зависит от команд, выполняемых в ассемблере / машинном коде. VS никогда не был хорош в C ++, и в течение многих лет Borland пинала их за эффективность и надежность. Затем Borland продал свой филиал IDE & C ++ как отдельную компанию.

Это также зависит от того, как вы запрограммировали процесс для выполнения в C ++, можете ли вы отредактировать, чтобы показать этот код?

Преимущество C # состоит в том, что он управляется и может использовать более высокий уровень интерпретации вашего кода, поэтому в фоновом режиме он может JIT код преобразовать всю строку в проанализированный формат, а затем разрывы цикла forчанки отключаются (1 шаг зацикливается) , поэтому, если вы напишите это в C ++, он будет следовать вашим командам более точно, даже если они менее эффективны, то есть: он разрывает фрагмент, который вы просматриваете, а затем преобразуетчто в разобранном формате (2 шага петли) .

Таким образом, используя приведенный выше пример, если предположить, что две команды вместе на 50% медленнее, чем две команды в C ++, но две команды обрабатываются в каждом цикле, где код c # обрабатывает только 1 команду вкаждый цикл, любая неэффективность будет усугубляться.

ТАКЖЕ +1 к размышлению в комментариях выше, ссылка на значение может иметь довольно большое значение, особенно когда вы имеете дело с большими наборами данных. Я думаю, что его ответ наиболее вероятен из-за больших различий.

Упрощение - это ответ, который я считаю:

std::string byteString = hex.substr(i, 2);
unsigned char byte = (unsigned char) strtol(byteString.c_str(), NULL, 16);

Может стать

unsigned char byte = (unsigned char) strtol(hex.substr(i, 2).c_str(), NULL, 16);

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...