ud1
> Ну видно, что они долго оптимизировали.
int RemoteProcessClient::readInt() { vector<signed char> bytes = this->readBytes( INTEGER_SIZE_BYTES); if ( this->isLittleEndianMachine( ) != LITTLE_ENDIAN_BYTE_ORDER) { reverse( bytes.begin( ), bytes.end( )); } int value; memcpy( &value, &bytes[0], INTEGER_SIZE_BYTES); return value; }
запустил профайлер:
![std::vector eat half of cputime in this method std::vector eat half of cputime in this method | [Russian AI Cup] CodeWars 2017](https://gamedev.ru/files/images/govno2.png)
ЗЫ: судя по первой картинке можно подумать: либо с java стороны байты по одной штуке приходят, либо на стороне с++ каждый раз буфер размером в 100кб создаётся just for lulz.
added: если бы я делал протокол и мне не мешали, то я бы старался за два syscall`а получать PlayerContext.
Adler
> ЗЫ: судя по первой картинке можно подумать: либо с java стороны байты по одной
> штуке приходят, либо на стороне с++ каждый раз буфер размером в 100кб создаётся
> just for lulz.
Где 100Кб? Посмотрел, byteCount передается ровно столько, сколько хотят прочесть. И насколько помню со старых контестов, со стороны жавы тоже нормально передавали, сразу сообщение целиком, не по байту. Код конечно можно еще облагородить, но в целом вроде норм.
counter воткнул:
int32 CSimpleSocket::Receive(int32 nMaxBytes) { ... if ( ( m_pBuffer != NULL) && ( nMaxBytes != m_nBufferSize)) { static int counter=0;counter++; // <------------------------- этот = 395856/209 = 1894.04 за кадр в среднем delete [] m_pBuffer; m_pBuffer = NULL; } ... switch ( m_nSocketType) { case CSimpleSocket::SocketTypeTcp: { do { static int counter=0;counter++; // <------------------------- этот = 649411/209 = 3107.22 за кадр в среднем m_nBytesReceived = RECV( m_socket, ( m_pBuffer + m_nBytesReceived), nMaxBytes, m_nFlags); TranslateSocketError( ); } while ( ( GetSocketError( ) == CSimpleSocket::SocketInterrupted)); break; } ...
RECV - это recv из Winsock2.h
они тут m_pBuffer пересоздают каждый раз когда надо что-то прочитать другого размера.
also: RemoteProcessClient::readBytes вызывается 494891/209=2367.89 раз за кадр в среднем.
итого:
выделений памяти: 1894+2367 = 4261 раз за кадр
вызов recv: 3107 раз за кадр // вместо двух, если бы всё было сделано идеально. да, на стороне java надо было бы посчитать весь размер заранее, но это должно быть быстрее чем делать дополнительные системные вызовы send/recv.
ЗЫ: ещё воткнул "static int recv_n=0;recv_n+=m_nBytesReceived>0?m_nBytesReceived:0;" в конец CSimpleSocket::Receive
результат: 2963076/209 = 14177.39 байт за кадр.
upd:
оптимизировал readVehicleUpdate
upd:
замерил среднее время выполнения readVehicleUpdates:
до: 6.55 ms/frame
после: 3.69 ms/frame
ud1
> И насколько помню со старых контестов, со стороны жавы тоже нормально
> передавали, сразу сообщение целиком, не по байту. Код конечно можно еще
> облагородить, но в целом вроде норм.
смотри, в среднем за кадр надо прочитать 14177.39 байт, но для этого делается 3107 системных вызов recv, то есть в среднем читается по 4.56 байта за один вызов recv.
это не нормально.
короче, я зафиксировал seed в "local-runner.default.properties", затем отрыл свою стратегию которая:
выделяет все юниты
добавил их в группу 42
приказывает им вращаться вокруг центра карты
а потом замерил среднее время выполнения readVehicleUpdates для 128 первых кадров: 31.4 ms/frame
стратегии вроде разрешено в среднем тратить только 20ms/frame
полностью переписал readVehicleUpdate:
VehicleUpdate RemoteProcessClient::readVehicleUpdate() { if( bool original_version=false) { if ( !readBoolean( )) { exit( 20013); } long long id = readLong( ); double x = readDouble( ); double y = readDouble( ); int durability = readInt( ); int remainingAttackCooldownTicks = readInt( ); bool selected = readBoolean( );//*/ vector<int> groups = readIntArray( ); return VehicleUpdate( id, x, y, durability, remainingAttackCooldownTicks, selected, groups); } #define LIST( ADD)\ ADD( bool,ok,$)\ ADD( long long,id,$)\ ADD( double,x,$)\ ADD( double,y,$)\ ADD( int,durability,$)\ ADD( int,remainingAttackCooldownTicks,$)\ ADD( bool,selected,$) //=== struct t_tmp{ bool trash_bef[8-1]; #define F( TYPE,NAME,VALUE)TYPE NAME; LIST( F); #undef F bool groups_size[8-1]; }; int size=sizeof( t_tmp)-( 8-1)*2+4; t_tmp tmp={0}; // if( bool need_fast_networking=true) { unsigned int offset=0; auto m_pBuffer=( char*)&tmp.ok; for( ;;) { if( offset>=size)break; auto receivedByteCount=recv( socket.m_socket,m_pBuffer+offset,size-offset,socket.m_nFlags); //socket.Receive(size-offset); if( receivedByteCount<=0)break; offset+=receivedByteCount; } if( offset!=size){ exit( 10012); } } #define F( TYPE,NAME,VALUE)auto&##NAME=tmp.NAME; LIST( F); #undef F #undef LIST if( !ok)exit( 20013); vector<int> groups;groups.resize( *( int*)&tmp.groups_size); if( bool need_fast_networking=true)if( !groups.empty( )) { int size=groups.size( )*4; unsigned int offset=0; auto m_pBuffer=( char*)&groups[0]; for( ;;) { if( offset>=size)break; auto receivedByteCount=recv( socket.m_socket,m_pBuffer+offset,size-offset,socket.m_nFlags); if( receivedByteCount<=0)break; offset+=receivedByteCount; } if( offset!=size){ exit( 10012); } } return VehicleUpdate( id, x, y, durability, remainingAttackCooldownTicks, selected, groups); }
у меня этот код работает на этом же тесте в среднем за 3.9ms/frame для 128 первых кадров.
ЗЫ: ещё я "оптимизировал" StatTimer.h: https://pastebin.com/zBjiHByb
Мне кажется на данный момент самая лучшая стратегия у GreanTea http://russianaicup.ru/profile/GreenTea
Квадратно-гнездовой способ битвы его стратегии проигрывает в чистую.
похоже инфа о том, кто что выделил и какой юнит в каких группах находиться присылается даже про оппонента.
added:
отправил pull request: https://github.com/Russian-AI-Cup-2017/cpp-cgdk/pull/3
Diversus
Да неплохо, но имхо тут ЯУ рашит не фиго, зря добавили
Adler
> отправил pull request: https://github.com/Russian-AI-Cup-2017/cpp-cgdk/pull/3
Надеюсь у них всё не так плохо с ревью кода, чтобы это прошло в продакшн.
ЕПРСТ объясните как пашет Scale плиз, а то из прввил не фига не въехал
UPD все разобрался я затупок команду перекрывал следующей за ней идущей
Что-то я не пойму, как перебрать свою технику в цикле?
Только через VechicleUpdate? А там и своя и чужая техника, или как?
Diversus
GetNewVehicles - содержит не изменяемые параметры "новой" техники в игре
Т.е. в начале игры этот вызов делается в тике 0 мы получаем всю существующую технику в игре (включая свою, и вражескую разница в PlayerID) (Так же как я понял в режиме "туман войны", вражеская техника в этот массив не попадает, а появляется в случае обнаружения). Так же технику с 0 здоровьем можно смело затирать
Далее по ходу игры вызываешь GetVehicleUpdates оно содержит изменяемые поля (x,y, здоровье, ну и ID техники ессесно)
По ID техники присваиваешь новые параметры своему массиву данных. Как-то так
сначала оптимизируешь время работы стратегии где вся тысяча объектов движется все 20000 кадров.
потом находишь тормозное говно в сетевой части стратегии.
исправляешь его.
отправляешь pull request
ждешь.
а потом тебе заявляют:
Мы не принимаем правки в автогенерённый код, так как их невозможно поддерживать в случае изменения клиента. Время работы пустой стратегии на C++ составляет всего несколько секунд при общем лимите в 210 секунд. Подобный уровень оптимизации излишен.
> Время работы пустой стратегии на C++
WTF?
ответил им:
вот в этой игре:
http://russianaicup.ru/game/view/30740
я использовал вот эту стратегию:
https://pastebin.com/raw/Npqqs1m4
посмотрите на время:
time consumed: 84.12 sec
time passed: 242.33 sec
peak memory: 4874240 bytes
что скажите?
PS: им придёт уведомление на комментарий в закрытом pull request`е? или это тоже самое что в /dev/null отправить?
Adler
Ща чую ты добьешся от них того, что число групп уменьшат. До 5 например.
Adler
Вот моя оптимизация, проверь, насколько стало быстрее:
https://github.com/Russian-AI-Cup-2017/cpp-cgdk/pull/5
Тема в архиве.