to_string быстрее snprintf(странность компилятора C++ от Microsoft)

184
08 сентября 2018, 12:50

Добивая тему про строчки(std::to_string без создания новой строки) внезапно обнаружил, что компилятор C++ от Microsoft(Microsoft (R) C/C++ Optimizing Compiler Version 19.14.26430 for x86) генерирует бинарник, в котором std::to_string быстрее snprintf более чем в 3 раза.
Пробовал запускать тот же код на ideone.com, onlinegdb.com (т.к. расчитывал, что там стоит g++), где snprintf оказалось шустрее примерно в 1.4 раза.
Я понимаю, что всё зависит от реализации стандартной библиотеки разными компиляторами, но такую пропасть я увидеть никак не ожидал. В чём загвоздка, кто посвящён в детали реализации stl от microsoft(или у меня в коде ошибка?) и объяснит совсем запутавшемуся.
Вот код:

#include <iostream>
#include <string>
#include <chrono>
using namespace std;
template<typename Callback>
float getElapsedTime(Callback cb) {
    auto start = chrono::system_clock::now();
    cb();
    auto end = chrono::system_clock::now();
    return chrono::duration_cast<chrono::milliseconds>(end - start).count() / 1000.f;
}
int main() {
    ios::sync_with_stdio(false);
    uint64_t start = 0;
    uint64_t end = 10'000'000;
    cout << "0 .. 10\'000\'000\n";
    cout << "snprintf: " << getElapsedTime([&]() {
        char buffer[1024] = {};
        for (uint64_t i = start; i < end; ++i)
            snprintf(buffer, 1024, "%llu", i);
    }) << "s\n";
    cout << "to_string: " << getElapsedTime([&]() {
        for (uint64_t i = start; i < end; ++i) {
            auto s = to_string(i);
        }
    }) << "s\n";
    start = 999'999'999'995'000'000;
    end = 1'000'000'000'010'000'000;
    cout << "\n999\'999\'999\'995\'000\'000 .. 1\'000\'000\'000\'010\'000\'000\n";
    cout << "snprintf: " << getElapsedTime([&]() {
            char buffer[1024] = {};
            for (uint64_t i = start; i < end; ++i)
                snprintf(buffer, 1024, "%llu", i);
        }) << "s\n";
        cout << "to_string: " << getElapsedTime([&]() {
            for (uint64_t i = start; i < end; ++i) {
                auto s = to_string(i);
            }
        }) << "s\n";    
    cin.get();
}

Вывод программы, сгенерированной компилятором microsoft(на домашнем компьютере с Windows 10):

0 .. 10'000'000
snprintf: 2.103s
to_string: 0.26s
999'999'999'995'000'000 .. 1'000'000'000'010'000'000
snprintf: 5.26s
to_string: 2.115s

Вывод с ideone:

0 .. 10'000'000
snprintf: 0.747s
to_string: 0.746s
999'999'999'995'000'000 .. 1'000'000'000'010'000'000
snprintf: 1.389s
to_string: 1.884s
Answer 1

В GCC & Clang, вызов std::to_string в конце концов вырождается в snprintf, тогда как в MSVC (по крайней мере в 2017) идёт прямой вызов внутренней функции _Integral_to_string. Эта функция выполняет обычный цикл, который делит число на 10 и составляет таким образом строку. Учитывая тот факт, что snprintf, вероятно, имеет схожую реализацию конвертирования числа в строку, получается, что std::to_string будет быстрее т.к.:

  1. Весь код конвертирования числа встраивается, тогда как snprintf вызывается через call.
  2. snprintf вынужден каждый раз разбирать строку форматирования, чтобы понять, что за число (или числа) и как его преобразовывать.

Именно второй пункт, скорее всего, и даёт такую разницу в производительности.

Answer 2

Не оправдывая VC++ :) - есть у меня подозрение, что, помимо платы за универсальность, дело еще и в том, что snprintf вызывается, а to_string встраивается. Покопайтесь там, у кого GCC под рукой - там snprintf случайно не встраивается?...

Для интереса добавлен вот такой простейший код вместо snprintf (да, да, я знаю, что для 0 он не сработает :))

void hands(char * buffer, uint64_t i)
{
    char * c = buffer;
    while(i) { *c++ = i%10 + '0'; i/=10; }
    for(--c; c > buffer; ++buffer, --c)
    {
        char s = *c;
        *c = *buffer;
        *buffer = s;
    }
}

Вызов встроился, и у меня результаты для этого варианта, snprintf и to_string соответственно около 730, 4830 и 1450 мс. Забавно, что никакие танцы с бубном - типа заставить s каждый раз переаллоцировать память - особо на время работы to_string не повлияли...

READ ALSO
boost::enable_if и static_cast

boost::enable_if и static_cast

Не могу понять как написать такую хотелку

199
Arduino: Не получается считать другие rfid карты

Arduino: Не получается считать другие rfid карты

Заказал себе rfid-RC522 считыватель для ардуино, подключил к меге, подключил библиотеку(MFRC522), залил скетч, но он работает только с брелком и карточкой...

190
Что лучше использовать: cout / wcout или printf? [закрыт]

Что лучше использовать: cout / wcout или printf? [закрыт]

Собственно, вопрос в заголовкеПожалуйста, дайте подробный ответ

223
Сортировка массивов и запись в файл

Сортировка массивов и запись в файл

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

234