Блог пользователя Estraaanged

Автор Estraaanged, 12 лет назад, По-русски

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

Как-то уже привык использовать только C++'шные cin и cout, ибо еще когда только учил язык только им уделялось внимание; были замечания, что использовать старые C'шные scanf-printf не стоит, лучше отдать предпочтение их "наследникам". Точнее говоря, я не просто привык их использовать, кроме них-то, я никогда и не пользовался ничем другим, потому что они (cin-cout) были, как минимум, проще для понимания мне, как начинающему программисту (хотя и сейчас я не слишком-то далеко ушел).

Собственно, сегодня решил дорешать некоторые задачи, и вот вспомнил про одну. Когда-то уже пытался сделать её, но был TL на большей части тестов, а в тот момент в голове никаких идей больше не было, ну и забил на неё временно. Сегодня вот решил вернуться и дорешать наконец.

В общем-то говоря, весь смысл этого поста в паре предложений ниже.

Сделал около полусотни попыток с разными мелкими оптимизациями (ну, насколько это можно так назвать) и вводом-выводом через cin-cout — Time Limit. Почти наугад решил попробовать C'шные функции ввода-вывода, потому видел здесь некоторые обсуждения на тему скорости их работы по сравнению с C++'шными потоками вывода. И каково же было моё удивление, когда задача залетела после этого с первой попытки, да еще и время ускорилось почти двукратно.

P.S. Пытался написать два слова через слеш \ как простой текст, но он затирается, почему-то. Например, эти два слова написаны через слэш: cin\cout. Если об этой проблеме уже где-то было написано, то извиняюсь, не видел.

  • Проголосовать: нравится
  • -3
  • Проголосовать: не нравится

»
12 лет назад, # |
  Проголосовать: нравится +15 Проголосовать: не нравится

Говорят, cin.sync_with_stdio(0); в начале программы здорово ускоряет ввод/вывод через cin/cout.

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +1 Проголосовать: не нравится

    Но только в GNU C++ — в MSVS не ускоряет.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      А что в MSVS ускоряет?

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится +1 Проголосовать: не нравится

        Я не знаю, но этот факт экспериментально проверен. С ios_base::sync_with_stdio(false); — GNU, MSVS Без ios_base::sync_with_stdio(false); — GNU, MSVS

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
          Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

          С ним есть несколько подстав. Например нельзя использовать endl потому что он сбрасывает флаги. И не только. Тормазит cout как раз из-за endl. Если его заменить на "\n" cout и printf одинаково примерно работают.

          • »
            »
            »
            »
            »
            »
            12 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится

            В коде выше нет endl, разница в 2 раза, при чем там считывание не главная часть задачи (считывается 4*10^5 интов).

            • »
              »
              »
              »
              »
              »
              »
              12 лет назад, # ^ |
                Проголосовать: нравится +2 Проголосовать: не нравится

              А я про вывод. Не понимаю я людей, которые принципиально не пользуются scanf. C сin тоже явно есть какая-то подстава почему он недобуферезованный.

          • »
            »
            »
            »
            »
            »
            12 лет назад, # ^ |
              Проголосовать: нравится +3 Проголосовать: не нравится

            Главная фишка endl что он делает fflush. т.е. все что записано в буфер сбрасывает на диск.

            • »
              »
              »
              »
              »
              »
              »
              12 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится

              Да. И именно поэтому он тормозит. И еще он заодно все флаги сбрасывает.

»
12 лет назад, # |
  Проголосовать: нравится -10 Проголосовать: не нравится

Можно писать ios_base::sync_with_stdio(false), но scant/printf все равно быстрее.

»
12 лет назад, # |
  Проголосовать: нравится +4 Проголосовать: не нравится

Самое время научиться использовать scanf/printf. Тем более, когда-то же надо начинать. И там нет ничего сложного, а программу они и правда ускоряют по сравнению с cin/cout.

»
12 лет назад, # |
Rev. 2   Проголосовать: нравится +18 Проголосовать: не нравится

Интересно услышать мнение людей, имеющих большой опыт и олимпиадного, и промышленного программирования, насчёт такой стороны вопроса.

Насколько я понимаю, использование scanf/printf приводит к потенциальным проблемам не только по причинам %lld versus %I64d. А ещё и потому, что возникает зависимость от типа в таких местах, где по современным методологиям программирования никаких зависимостей быть не должно. Писать template-ом потребует о-о-очень больших извращений, при ручной замене типа возникает лишнее место где можно налажать, и т д

Разумеется, это не отменяет факта, что действительно бывают ситуации, где scanf/printf таки заметно ускоряет.

Так что, IMHO, scanf/printf должны быть исключением (а не правилом), ориентированным только на случаи: (а) ввод/вывод критично влияют на время работы программы в целом; (б) есть достаточные гарантии, что типы не будут меняться, и при этом нужно форматирование, которое printf-ом делается таки проще, например printf("%d %2d:%02d:%02d.%03d",days,hours,minutes,seconds,milliseconds).

Кстати, ещё одна ситуация, где scanf/printf-ами реально можно налажать — написать в Visual Studio %lf для long double, а потом удивляться, почему не работает под другими компиляторами.

  • »
    »
    12 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится +12 Проголосовать: не нравится

    Я считаю, что промышленное и олимпиадное программирование очень сильно отличаются, поэтому мне кажется нормальным сделать различные правила. И так как для олимпиад я стараюсь следовать логике "меньше случаев => меньше думать, меньше кода => меньше шанс ошибиться", то считаю правильным в олимпиадах всегда использовать scanf/printf. Они не только быстрее, но и позволяют легко выводить/вводить в каком-нибудь отвратительном формате, что с cin/cout потребует несколько больше телодвижений. Особенно это критично при debug output'е.

    Что же до совместимости разных компиляторов и переиспользования — в олимпиадах время жизни кода редко превышает 24 часа, так что об этом я вообще не беспокоюсь. Какая мне разница, что кто-то когда-нибудь не сможет заставить работать решение участника? Мне гораздо важнее сдать быстро и безбажно в одних конкретных условиях.

    С точки зрения промышленного программирования я ничего сказать не могу.

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +4 Проголосовать: не нравится

    Мне кажется, что ни scanf/printf, ни cin/cout к промышленному программированию практически не имеют отношения. Промышленная разработка консольных приложений — это для меня что-то труднопредставимое. Работа с файлами более вероятна, но и там это скорее двоичные файлы, а для стуктурированных данных — базы данных.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      Речь, скорее, о C-style IO (printf/scanf) против C++-style IO (istream/ostream). Это не только консоль и файлы, это еще и сериализация объектов в строковое/текстовое представление. Довольно часто это нужно и в промышленном программировании.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится +7 Проголосовать: не нравится

      Хм...

      $ find /usr/src/linux-2.6.27 -type f -name '*.c' | while read f; do grep -E 'scanf|printf' $f; done | wc -l
      18935
      

      Разработку ядра linux Вы не считаете промышленным программированием?

      • »
        »
        »
        »
        12 лет назад, # ^ |
        Rev. 2   Проголосовать: нравится +6 Проголосовать: не нравится

        А ничего, что это С, а не С++?

        P.S. Извиняюсь, думал, что это ответ на другой комментарий :)

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
          Rev. 4   Проголосовать: нравится +2 Проголосовать: не нравится

          Это был комментарий к утверждению, что "ни scanf/printf, ни cin/cout к промышленному программированию практически не имеют отношения". ;)

          P.S. Или по-вашему программирование на C по определению промышленным программированием быть не может, а предназначен только для студентов, которые на лавочках opensource ваяют?

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится

        Я не говорил, что нельзя привести примеры использования этих функций или потоков в промышленном программировании. Просто процент таких случаев очень мал, по-моему, по отношению ко всему промышленному программированию на С/С++.

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
          Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

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

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится +1 Проголосовать: не нравится

      Под линукс часто делаются консольные приложения, ибо легко перенаправить вывод одной в ввод другой, или всяким утилитам типа grep, awk. Это довольно удобно, и используется повсеместно. Еще такие программы можно использовать в скриптах, запускать и получать вывод в несколько строк кода, затем снова выводить в стандартный вывод. Из нагромождения таких скриптов и программ получаются например CGI скрипты строящие веб страницу.

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Есть и другие причины использования iostream в production, кроме buffer overflow и отсутствия типобезопасности. Перегрузив operator<< и operator>> в классе вы получаете средство сериализации объектов, которое можно использовать, например, вот так:

    template<typename T> ostream& toLog(ostream& ostr, const T& value) 
    {
      return ostr << value;
    }
    
»
12 лет назад, # |
Rev. 7   Проголосовать: нравится 0 Проголосовать: не нравится

сin и cout используют потоки, должны ещё определить тип переменной с которой работают. В scanf и printf мы указываем тип переменной и поэтому scanf и printf не тратят время на определение типа пременной. Они не используют потоки. Засчёт всего этого scanf и printf выигрывают время. Но у scanf есть недостаток. Если нужно ввести массив букв известного размера, элементы которого разделены пробелом то scanf считает пробел за элемент массива и нужно писать проверку на пробел в таком случае. ( а у cin такой проблемы нет) . Зато с помощью scanf и printf можно легко узнать код почти любого символа (даже пробела и клавиши Enter).

' #include ' ' #include '
' using namespace std; ' ' int main()
' { ' ' char c; ' ' scanf("%c",&c); ' ' printf("%d",c); ' ' return 0; ' ' } '

  • »
    »
    12 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

    как раз таки cin/cout оопределяет тип на этапе компиляции(1 раз), а вот scanf/printf в рантайме.

    скипать/не скипать пробелы тоже можно и там и там.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      scanf,printf не определяют тип. Они берут указатель со стека и записывают столько байт сколько сказали. А если ты им подсунул что-то не то — это твои проблемы.

      • »
        »
        »
        »
        12 лет назад, # ^ |
        Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

        При этом он должен распарсить строку формат, не? (Хотя не удивлюсь, если сейчас это для простых форматов уже оптимизируется)

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
          Rev. 2   Проголосовать: нравится +5 Проголосовать: не нравится

          Ну это не называется распознать тип. Хотя если формат какой-нибудь printf("%+028I64x"), то да это может быть не тривиально.

          • »
            »
            »
            »
            »
            »
            12 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится

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

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    1. Используйте ` для кода. Он будет нормально показываться. А большой, жирный, еще и со скушанным всем подряд это ужасно.
    2. scanf(" %c") прекрасно пропускает пробельные символы.
    3. Определение типа переменной происходит на этапе компиляции. В зависимости от этого происходит вызов разных версий перегруженной функций operator>>(istream&,T)
    4. cin,cout не используют потоки, а ими являются. Если вы конечно не имели ввиду stdin и stdout. Но их используют и scanf/printf.
  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    А для более чем однострочного кода отбивайте его от края несколькими пробелами(4 точно хватает)

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится +3 Проголосовать: не нравится

      +1 AlexDmitriev (хороший совет) и +1 kuniavski. Спасибо за информацию. Пункт №4, №3 — для меня открытие. Спасибо за пункт №1 и особенно за пункт №2 ( проверил только что, работает ).

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится +3 Проголосовать: не нравится

        А еще, если нажать кнопку "Ред." в блоке вашего сообщения, то его можно изменить для лучшей читаемости:)

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
          Rev. 2   Проголосовать: нравится +3 Проголосовать: не нравится

          +1 спасибо, но iostream и cstdio так и не отобразились, к сожалению, не смотря на 4 пробела после и до строчек.

»
12 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Раз уже эта извечная тема не утихает, спрошу здесь заодно. Влияет ли на скорость считывания/записи изменение размера внутреннего буфера потока в iostreams? Теоретически, если мы знаем, что будет много данных, мы можем пробовать считать больше за один системный вызов и таким образом экономить время.

Как-то так:

const int bufsize = 64 * 1024;

char *buf = new char[bufsize];

cin.rdbuf()->pubsetbuf(buf, bufsize);

У себя (GCC, Linux) я пробовал разные значения от 1 до 10M и не обнаружил никакой разницы на больших массивах данных, что представляется весьма странным (pubsetbuf возвращает ОК).