iakolzin's blog

By iakolzin, 8 years ago, In Russian,

Всем доброго времени суток. Сегодня я хочу рассказать вам одну очень поучительную и, увы, немного печальную историю. Я думаю, что всем, кто пишет на C++, будет полезно прочитать.

Итак, всего-то неделю назад мы вернулись с Чемпионата Урала. Как вы могли прочитать в письмах и обзорах, на чемпионате было два тура: игровой и основной. И если с основным все понятно, то с игровым все вышло весьма презабавно. Началась эта история с того, что, когда на подведении итогов тура показали результаты швейцарки, то мы не увидели себя в первых ~30 командах, которые поместились на основной монитор. Хотя наш бот был не слишком умный, но все же трудно было предположить, что все написали лучше. Мысль о том, что он где-то упал, отпала, т.к. и локально, и на сервере компилятор был одинаковый, а, значит, никаких "трудностей перевода". Ну да всякое бывает, на следующий день был основной тур, и мы забили.

Пару дней назад появились результаты. Увидев нашего бота на предпоследнем месте, удивлению нашему не было предела, т.к., если верить монитору, то он проиграл даже SampleBot'у, которого раздали всем командам. Это было просто невероятно, мы все-таки догадались против него поиграть. Опять же, падение почти исключалось, т.к. у кого-то он смог выиграть, т.е. хотя бы ход сделать успевал. Естественно мы скачали исходник, скомпилили, запустили и... Он выигрывал! Невероятно, но это был "тот самый" наш, самый что ни на есть разумный бот! Выиграв у команды N из топ-5(!!!), я решил наконец-то посмотреть, что же не так. К счастью, доступны были и логи проведенных на сервере матчей. Что же я увидел? Вместо нашей программы кто-то тупо складывал все шарики в левый столбец и через 16 ходов проигрывал. Естественно, мы подумали, что это какая-то нелепая ошибка и сразу написали организаторам.

Как вы думаете, мог ли ответ удивить нас еще сильнее, чем все произошедшее? Да, ответ буквально поверг в шок. Оказывается, тестирование на сервере проходило не совсем как локально, а именно, флаг /GL, который в студии в релизе включен по умолчанию, был отключен на сервере, и, утверждалось, что именно это было причиной столь странного поведения. Действительно, отключив этот флаг у себя, я получил то же, что и в том самом печальном логе. Кстати, в дебаге этот флаг тоже отключен (и включить его нельзя), однако она работала нормально. День прошел в поиске ошибки, стоит ли говорить, сколько вариантов ошибок я пытался найти?

Оказалось все довольно прозаично: наша программа делала перебор на два хода, соответственно, делала ход один раз, изменяя при этом игровое поле, потом ходила еще раз. Вот строчка: int score = FirstMove(tmp) + SecondMove(tmp); tmp - игровое поле, в функции передавалось по ссылке. Кажется, что все логично, но не тут-то было! Опытным путем было установлено, что в релизе в зависимости от этого пресловутого ключика (/GL) зависит порядок вызова функций. Т.е. у нас всегда функции вызывались в правильном (для людей, пишущих слева направо) порядке, а на сервере - в обратном. Разумеется, как только я разнес эту конструкцию на две операции, все стало работать. Вот такая вот подстава.

На самом деле, это не совсем непредсказуемая подстава. Ведь мы знали, что, например, в случае Func(a(), b()); сначала вызовется именно b(), а только потом a(). То есть такой код уже потенциально опасен. Засада в том, что программа работает правильно именно в случае по умолчанию, а падает в не совсем обычной ситуации. Тем не менее, это послужит хорошим уроком. Может быть, получить такую проблему при разработке большого проекта - зло еще большее, чем потеря приза.

Оптимизатор - очень хитрая штука, он совершенно изменяет вашу программу. Как машина, он предельно логичен, и нельзя с уверенностью сказать, баг ли это, или специфика машинной логики. К сожалению, он в любом случае не знает, какой логикой вы руководствуетесь при написании кода. Поэтому старайтесь использовать в своем коде наиболее "простые" конструкции, которые не могут иметь двоякого толкования.

Кроме того, выскажу свое пожелание организаторам игровых туров. Понятно, что проводить полное тестирование при каждой сдаче невозможно, да и не нужно. Но вполне можно было протестировать решение участников хотя бы на Sample-Bot'е. Знания вердикта, времени, памяти, количества ходов и рандсида было бы с высокой вероятностью достаточно для понимания того, работает ли программа на сервере так же, как и локально. Понятно, что такой случай, как у нас, было сложно предположить, но это пожелание на будущее.

 
 
 
 
  • Vote: I like it
  • +66
  • Vote: I do not like it