NicoAZ
> Попытка что-то начать доказывать - это изначально было ошибкой с моей стороны.
Не понятно зачем доказывать, когда можно написать релевантный нагрузочный тест и измерить. Очевидно, нерелевантный тест будет измерять свое качество а не интересующую нас производительность.
Вий
> Не понятно зачем доказывать, когда можно написать релевантный нагрузочный тест и измерить. Очевидно, нерелевантный тест будет измерять свое качество а не интересующую нас производительность.
Как я уже сказал, я умываю руки. Вы можете продолжать спор, если интересно.
NicoAZ
> Вы можете продолжать спор, если интересно.
Какой спор то? О чем?
NicoAZ
> Как я уже сказал, я умываю руки
Ну вот, сначала сам спросил, что не так с тестом, а когда ему рассказали, как его оптимизировать, ускорив раз в 8, сбежал. Хотя сначала вроде хотел попробовать себя в разработке высокопроизводительных игровых серверов.
Вечером я запущу и первоначальный вариант и улучшенный и покажу результаты. Вечер у меня часов через 8
итак, первый тест (пришлось просить нейросеть дописать тест)
#include <iostream> #include <vector> #include <chrono> #include <cstring> #include <cstdint> #include <string> #include <memory> #define STR1_MAX_LEN 20 #define STR2_MAX_LEN 30 #define NUM_STRUCTURES 100000 #define NUM_ITERATIONS 100000 // Сырая структурка struct MyData { char string1[STR1_MAX_LEN]; char string2[STR2_MAX_LEN]; int int1; int int2; int int3; }; // Структурка, которую шлем struct EnhancedData { MyData payload; // Сырая uint32_t payload_hash; // Хеш сырой uint64_t generation_time; // Время создания }; // Хешируем. Не продакшн риди, открыт для предложений. uint32_t calculate_data_hash(const MyData* data) { uint32_t hash = 1234; const auto* ptr = reinterpret_cast<const unsigned char*>( data); size_t len = sizeof( MyData); for ( size_t i = 0; i < len; ++i) { hash = ( ( hash << 5) + hash) + ptr[i]; } return hash; } // Полная функция сериализации (включает подготовку данных + сериализацию) size_t serialize_data_for_transmission( const MyData* original_data, unsigned char* buffer, size_t buffer_size) { constexpr size_t required_size = sizeof( EnhancedData); if ( buffer_size < required_size) { return 0; } // Подготавливаем EnhancedData EnhancedData enhanced; enhanced.payload = *original_data; enhanced.payload_hash = calculate_data_hash( original_data); auto now = std::chrono::system_clock::now( ); auto timestamp = std::chrono::duration_cast<std::chrono::seconds>( now.time_since_epoch( )).count( ); enhanced.generation_time = static_cast<uint64_t>( timestamp); // Сериализуем в буфер std::memcpy( buffer, &enhanced, required_size); return required_size; } int main( ) { std::cout << "Выделение памяти для " << NUM_STRUCTURES << " структур...\n"; // Выделяем память для массива структур auto data_array = std::make_unique<MyData[]>( NUM_STRUCTURES); // Заполняем массив разными данными for ( int i = 0; i < NUM_STRUCTURES; i++) { std::memset( &data_array[i], 0, sizeof( MyData)); std::snprintf( data_array[i].string1, STR1_MAX_LEN, "String_%d", i); std::snprintf( data_array[i].string2, STR2_MAX_LEN, "Data_%d_important", i); data_array[i].int1 = i + 228; data_array[i].int2 = -666 - i; data_array[i].int3 = 1488 + i * 2; } // Выделяем буфер для сериализации auto serialize_buffer = std::make_unique<unsigned char[]>( sizeof( EnhancedData)); std::cout << "Начинаем тест сериализации " << NUM_ITERATIONS << " операций...\n"; // Переменные для предотвращения оптимизации uint64_t checksum = 0; size_t total_bytes = 0; // Тест полной сериализации (подготовка + хеширование + сериализация) auto start_time = std::chrono::high_resolution_clock::now( ); for ( int i = 0; i < NUM_ITERATIONS; i++) { // Используем разные структуры из массива int data_index = i % NUM_STRUCTURES; size_t serialized_size = serialize_data_for_transmission( &data_array[data_index], serialize_buffer.get( ), sizeof( EnhancedData)); // Используем результат сериализации чтобы предотвратить оптимизацию total_bytes += serialized_size; // Добавляем несколько байт из буфера в контрольную сумму checksum += serialize_buffer[0] + serialize_buffer[serialized_size/2] + serialize_buffer[serialized_size-1]; } auto end_time = std::chrono::high_resolution_clock::now( ); auto duration = std::chrono::duration_cast<std::chrono::microseconds>( end_time - start_time); // Результаты std::cout << "Время: " << duration.count( ) << " мкс (" << static_cast<double>( duration.count( )) / 1000.0 << " мс)\n"; std::cout << "Среднее время: " << static_cast<double>( duration.count( )) / NUM_ITERATIONS << " мкс\n"; std::cout << "Скорость: " << static_cast<double>( NUM_ITERATIONS) * 1000000.0 / duration.count( ) << " оп/сек\n"; // Выводим данные чтобы оптимизатор не удалил вычисления std::cout << "Контрольная сумма: " << checksum << ", общий объем: " << total_bytes << " байт\n"; return 0; }
output
Выделение памяти для 100000 структур... Начинаем тест сериализации 100000 операций... Время: 16992 мкс (16.992 мс) Среднее время: 0.16992 мкс Скорость: 5.88512e+06 оп/сек Контрольная сумма: 8300000, общий объем: 8000000 байт
Вий
> Время: 16992 мкс (16.992 мс)
Ну отлично, да. Это чисто замаршаллить структурку с подсчётом хеша. Просто великолепно. Могу лишь в очередной раз успехов пожелать. :)
А вот я сделал самую крошечную оптимизацию функции хеширования:
#include <iostream> #include <vector> #include <chrono> #include <cstring> #include <cstdint> #include <string> #include <memory> #define STR1_MAX_LEN 20 #define STR2_MAX_LEN 32 #define NUM_STRUCTURES 100000 #define NUM_ITERATIONS 100000 // Сырая структурка struct MyData { int int1; int int2; int int3; char string1[STR1_MAX_LEN]; char string2[STR2_MAX_LEN]; }; // Структурка, которую шлем struct EnhancedData { MyData payload; // Сырая uint32_t payload_hash; // Хеш сырой uint64_t generation_time; // Время создания }; // Хешируем. uint32_t calculate_data_hash(const MyData* data) { uint64_t hash = 1234; size_t len = sizeof( MyData); for ( size_t i = 0; i < len; i+=8) { uint64_t tmp; memcpy( &tmp, ( char*)data + i, 8); hash = ( ( hash << 5) + hash) + tmp; } return ( uint32_t)( hash ^ ( hash >> 32)); } // Полная функция сериализации (включает подготовку данных + сериализацию) size_t serialize_data_for_transmission( const MyData* original_data, unsigned char* buffer, size_t buffer_size) { constexpr size_t required_size = sizeof( EnhancedData); if ( buffer_size < required_size) { return 0; } // Подготавливаем EnhancedData EnhancedData enhanced; enhanced.payload = *original_data; enhanced.payload_hash = calculate_data_hash( original_data); auto now = std::chrono::system_clock::now( ); auto timestamp = std::chrono::duration_cast<std::chrono::seconds>( now.time_since_epoch( )).count( ); enhanced.generation_time = static_cast<uint64_t>( timestamp); // Сериализуем в буфер std::memcpy( buffer, &enhanced, required_size); return required_size; } int main( ) { std::cout << "Выделение памяти для " << NUM_STRUCTURES << " структур...\n"; // Выделяем память для массива структур auto data_array = std::make_unique<MyData[]>( NUM_STRUCTURES); // Заполняем массив разными данными for ( int i = 0; i < NUM_STRUCTURES; i++) { std::memset( &data_array[i], 0, sizeof( MyData)); std::snprintf( data_array[i].string1, STR1_MAX_LEN, "String_%d", i); std::snprintf( data_array[i].string2, STR2_MAX_LEN, "Data_%d_important", i); data_array[i].int1 = i + 228; data_array[i].int2 = -666 - i; data_array[i].int3 = 1488 + i * 2; } // Выделяем буфер для сериализации auto serialize_buffer = std::make_unique<unsigned char[]>( sizeof( EnhancedData)); std::cout << "Начинаем тест сериализации " << NUM_ITERATIONS << " операций...\n"; // Переменные для предотвращения оптимизации uint64_t checksum = 0; size_t total_bytes = 0; // Тест полной сериализации (подготовка + хеширование + сериализация) auto start_time = std::chrono::high_resolution_clock::now( ); for ( int i = 0; i < NUM_ITERATIONS; i++) { // Используем разные структуры из массива int data_index = i % NUM_STRUCTURES; size_t serialized_size = serialize_data_for_transmission( &data_array[data_index], serialize_buffer.get( ), sizeof( EnhancedData)); // Используем результат сериализации чтобы предотвратить оптимизацию total_bytes += serialized_size; // Добавляем несколько байт из буфера в контрольную сумму checksum += serialize_buffer[0] + serialize_buffer[serialized_size/2] + serialize_buffer[serialized_size-1]; } auto end_time = std::chrono::high_resolution_clock::now( ); auto duration = std::chrono::duration_cast<std::chrono::microseconds>( end_time - start_time); // Результаты std::cout << "Время: " << duration.count( ) << " мкс (" << static_cast<double>( duration.count( )) / 1000.0 << " мс)\n"; std::cout << "Среднее время: " << static_cast<double>( duration.count( )) / NUM_ITERATIONS << " мкс\n"; std::cout << "Скорость: " << static_cast<double>( NUM_ITERATIONS) * 1000000.0 / duration.count( ) << " оп/сек\n"; // Выводим данные чтобы оптимизатор не удалил вычисления std::cout << "Контрольная сумма: " << checksum << ", общий объем: " << total_bytes << " байт\n"; return 0; }
и вышло вот так вот
Выделение памяти для 100000 структур... Начинаем тест сериализации 100000 операций... Время: 6932 мкс (6.932 мс) Среднее время: 0.06932 мкс Скорость: 1.44259e+07 оп/сек Контрольная сумма: 18038548, общий объем: 8000000 байт
стало быстрее в 2 раза, 69 наносекунд на запись на raspberry pi 4
NicoAZ
> Ну отлично, да.
Да вроде неплохо, 8 мегабайт за 7 мс, это 1.14 гигабайта в секунду.
Сеть, напомню, пропустить может только 1 гигабит в секунду, а в гигабайте их целвых 8.
Вий
> Да вроде неплохо, 8 мегабайт за 7 мс, это 1.14 гигабайта в секунду.
> Сеть, напомню, пропустить может только 1 гигабит в секунду, а в гигабайте их целвых 8.
Ну я ж и говорю, если задача сервера - брать данные из переменной, хешировать и отправлять - мощности хватит. Вы попробуйте что-то помимо этого добавить. Ну, например, начать можете с того, что не просто из переменной берите статические данные, а каждый раз рандомные инты генерируйте... Это, конечно, даже близко не то, что реальный игровой сервер будет делать, но хоть что-то уже...
А если исправить в коде еще пару явных косяков произвоидительности
#include <iostream> #include <vector> #include <chrono> #include <cstring> #include <cstdint> #include <string> #include <memory> #define STR1_MAX_LEN 20 #define STR2_MAX_LEN 32 #define NUM_STRUCTURES 100000 #define NUM_ITERATIONS 100000 // Сырая структурка struct MyData { int int1; int int2; int int3; char string1[STR1_MAX_LEN]; char string2[STR2_MAX_LEN]; }; struct Footer { uint32_t payload_hash; // Хеш сырой uint64_t generation_time; // Время создания }; // Структурка, которую шлем struct EnhancedData { MyData payload; // Сырая Footer footer; }; // Хешируем. uint32_t calculate_data_hash(const MyData* data) { uint64_t hash = 1234; size_t len = sizeof( MyData); for ( size_t i = 0; i < len; i+=8) { uint64_t tmp; memcpy( &tmp, ( char*)data + i, 8); hash = ( ( hash << 5) + hash) + tmp; } return ( uint32_t)( hash ^ ( hash >> 32)); } // Полная функция сериализации (включает подготовку данных + сериализацию) size_t serialize_data_for_transmission( const MyData* original_data, unsigned char* buffer, size_t buffer_size, uint64_t ts) { constexpr size_t required_size = sizeof( EnhancedData); if ( buffer_size < required_size) { return 0; } // Подготавливаем EnhancedData Footer enhanced; enhanced.payload_hash = calculate_data_hash( original_data); enhanced.generation_time = ts; // Сериализуем в буфер std::memcpy( buffer, original_data, sizeof( MyData)); std::memcpy( buffer + sizeof( MyData), &enhanced, sizeof( Footer)); return required_size; } int main( ) { std::cout << "Выделение памяти для " << NUM_STRUCTURES << " структур...\n"; // Выделяем память для массива структур auto data_array = std::make_unique<MyData[]>( NUM_STRUCTURES); // Заполняем массив разными данными for ( int i = 0; i < NUM_STRUCTURES; i++) { std::memset( &data_array[i], 0, sizeof( MyData)); std::snprintf( data_array[i].string1, STR1_MAX_LEN, "String_%d", i); std::snprintf( data_array[i].string2, STR2_MAX_LEN, "Data_%d_important", i); data_array[i].int1 = i + 228; data_array[i].int2 = -666 - i; data_array[i].int3 = 1488 + i * 2; } // Выделяем буфер для сериализации auto serialize_buffer = std::make_unique<unsigned char[]>( sizeof( EnhancedData)); std::cout << "Начинаем тест сериализации " << NUM_ITERATIONS << " операций...\n"; // Переменные для предотвращения оптимизации uint64_t checksum = 0; size_t total_bytes = 0; // Тест полной сериализации (подготовка + хеширование + сериализация) auto start_time = std::chrono::high_resolution_clock::now( ); auto now = std::chrono::system_clock::now( ); auto timestamp = std::chrono::duration_cast<std::chrono::seconds>( now.time_since_epoch( )).count( ); uint64_t ts = static_cast<uint64_t>( timestamp); for ( int i = 0; i < NUM_ITERATIONS; i++) { // Используем разные структуры из массива int data_index = i % NUM_STRUCTURES; size_t serialized_size = serialize_data_for_transmission( &data_array[data_index], serialize_buffer.get( ), sizeof( EnhancedData), ts); // Используем результат сериализации чтобы предотвратить оптимизацию total_bytes += serialized_size; // Добавляем несколько байт из буфера в контрольную сумму checksum += serialize_buffer[0] + serialize_buffer[serialized_size/2] + serialize_buffer[serialized_size-1]; } auto end_time = std::chrono::high_resolution_clock::now( ); auto duration = std::chrono::duration_cast<std::chrono::microseconds>( end_time - start_time); // Результаты std::cout << "Время: " << duration.count( ) << " мкс (" << static_cast<double>( duration.count( )) / 1000.0 << " мс)\n"; std::cout << "Среднее время: " << static_cast<double>( duration.count( )) / NUM_ITERATIONS << " мкс\n"; std::cout << "Скорость: " << static_cast<double>( NUM_ITERATIONS) * 1000000.0 / duration.count( ) << " оп/сек\n"; // Выводим данные чтобы оптимизатор не удалил вычисления std::cout << "Контрольная сумма: " << checksum << ", общий объем: " << total_bytes << " байт\n"; return 0; }
То получается
Выделение памяти для 100000 структур... Начинаем тест сериализации 100000 операций... Время: 2098 мкс (2.098 мс) Среднее время: 0.02098 мкс Скорость: 4.76644e+07 оп/сек Контрольная сумма: 18050942, общий объем: 8000000 байт
а это уже 21 наносекунда на запись
или 3.8 гигабайта в секунду
Это примерно в 10 раз быстрее, чем предложенный тобой код. Вот поэтому я и говорю, что найти крутых программистов тяжело. Но ничего, я уже привык.
Сколько я там обещал гигабайт в секунду у raspberry pi, напомни?
NicoAZ
> Вы попробуйте что-то помимо этого добавить.
Ну так ты напиши юнит-тест в котором будет делаться то что тебе хочется протестировать, я запущу, покажу результаты, расскажу где косяки в тесте, если будут. Генерировать случайные числа - довольно медленная и тяжелая задача, но при помощи Вихря Мерсенна можно получить впечатляющие скорости, более чем достаточные для наших задач.
вместо генерации случайных чисел можно добавить 100 тыс наборов чисел:пары координат x,y , HP,MP,EP,ARM, и ещё 50 - они будут имитировать параметры персонажей +надетые вещи+эффекты на персонаже, и меняй их на 1...100 два раза в секунду. сколько займет времени? поменять 10 лямов чисел в секунду, как повлияет на твои 0.021 мкс?
Airumi
Как Вы могли заметить, у нас тут такой случай, когда информацию надо очень дозировано подавать. :) То время, что он получил - это вообще время маршаллинга одной структурки в другую. До того, что первая структурка должна откуда-то появиться, а вторая - куда-то отправиться, это мы так далеко пока даже не заходили. :)
NicoAZ
> То время, что он получил - это вообще время маршаллинга одной структурки в другую.
Как странно, ты ведь до этого считал что и это невозможно сделать достаточно быстро:
NicoAZ
> Давайте я Вам прям сегодня ради интереса напишу программку на Сях, которая будет просто подгружать какой нибудь массив структурок, а потом эти структуру маршаллить в минимально пригодный для передачи формат. Даже отправлять ничего никуда не будет, чисто маршаллинг. Там посмотрите, сколько будет один пакет ресурсов жрать чисто на создание, без какой либо бизнес-логики, без отправки, без ничего. Дайте знать, если интересно - займусь.
> Мне реально интересно было бы конкретные идеи по этому поводу послушать. Как и чего конкретно Вы реализуете, чтобы обойти описанные мной грабли.
Переобуваемся в воздухе? Я показал что граблей просто нет, пишешь высокопроизводительный код и все работает.
Боюсь, проблема не в задаче, а в том, что вы не умеете писать высокопроизводительный код. И вместо того чтобы учиться, пытаетесь доказать что учиться нечему. Это печально.
Вий
> Как странно, ты ведь до этого считал что и это невозможно сделать достаточно быстро
Ну так и не возможно.
Вий
> Я показал что граблей просто нет, пишешь высокопроизводительный код и все работает.
Что работает? Маршаллинг одной структуры в другую, даже без передачи? Работает, вопросов нет. Знаете, какой процент от общего количества потребляемых ресурсов этот процесс вообще обычно занимает?
Вий
> Боюсь, проблема не в задаче, а в том, что вы не умеете писать высокопроизводительный код. И вместо того чтобы учиться, пытаетесь доказать что учиться нечему. Это печально.
Мы Ваш код обсуждаем. Где совершенно очевидно, что 100 000 онлайна на RPi 4 держать не получится прям вот вообще никак, даже не близко, даже с задержкой риакции на действие в несколько секунд.
Airumi
> вместо генерации случайных чисел можно добавить 100 тыс наборов чисел:пары координат x,y , HP,MP,EP,ARM, и ещё 50 - они будут имитировать параметры персонажей +надетые вещи+эффекты на персонаже, и меняй их на 1...100 два раза в секунду. сколько займет времени?
Примерно столько же, сколько занимает сложение в функции хеширования. Там ведь сейчас делается по сути то что тебя интересует на скорости 3.8 гигабайта в секунду