Закончив очередной проект на Java, я попытался разобраться в причинах накопившегося раздражения. Да я люблю Яву и все такое, но… Есть несколько «но», которые досаждают. Приходится писать довольно много шаблонного кода, с генерацией которого вполне может справиться сам компилятор, IDE, конечно, выручает, но это не решение проблемы, а скорее костыль: если что-то изменилось, нужно перегенерить и вычистить и т.д. Проверки на null! Это зубная боль, по-хорошему, нужно делать их всегда дабы не нарваться на «нежданчик» в виде NullPointerException в самый неподходящий момент. Короче говоря, появилось желание посмотреть, что еще появилось в природе и сможет ли это нечто заменить мне Java. Дальше имеет смысл описать участников данного сравнения. Сразу скажу, что не претендую на полноту анализа, к сожалению, у меня было слишком мало времени, чтобы как следует познакомиться с каждым языком.

Обязательные требования к претендентам, которые у меня были:

• Язык общего назначения
• Кроссплатформенность (хотя бы Windows/Linux)
• Стабильность
• Статическая типизация
• Автоматическая уборка памяти
• Поддержка полноценной объектно-ориентированной парадигмы
• Хорошая поддержка в IDE (Eclipse, IDEA или на худой конец NetBeans)
• Безгемморойный доступ к существующим фреймворкам/библиотекам
• Производительность на уровне Java

Помимо этого очень хотелось бы:

• Null pointer безопасность обеспечивающаяся языком на этапе компиляции
• Простые приведения типов без рудиментов из С
• Возможность разумной перегрузки операторов
• Чтобы, наконец-то, были почищены базовые типы. Ну на фига тянуть в новый язык из бородатых времен всякие float/double, short/int/long? Может это и имело смысл во времена i386 процессоров, которые могли иметь математический сопроцессор, а могли и нет. Все эти тонкости могут жить в ядре операционной системы поближе к железу и не высовываться наверх. Базовые сущности: Integer и Float вот и все, что должно быть, детали пусть рулит компилятор.

• Гармоничное сосуществование лямбд
• Общий продуманный и целостный дизайн языка
• Выразительность, поменьше ASCII арта и загадочных конструкций, на которые хочется побрызгать святой водой
• Нормальные строки, в которых символы представлены не суррогатными парами или россыпью байтов, а полноценными Unicode point-ами.

Должен отметить, что в обзор не попал целый пласт языков, которые компилируются в нативные бинари или в C. Возможно, они и замечательные, но попробовав несколько вариантов типа D, Nim, Eiffel, Rust, Go, и еще чего-то я пришел к выводу, что не буду углубляться в эти изыскания. Проверка была очень простая – HelloWord. Бинарь получившийся на выходе обычно измерялся мегабайтами. Т.е. маленькая програмулька, которая печатает в консоль тупую строчку и должна занимать от силы 3-4Кб весит мегабайты! Мелочь скажете вы. Да мелочь, я понимаю, создатели языка впихивают в бинари всю мощь и богатство своего детища, но можно же было как-то оптимизировать это!? Если требуется только печать, зачем запузыривать в бинарь все что есть на свете? Ну вынесите минимально необходимую часть в статическую библиотеку, а все остальное в динамические либы. Если не ошибаюсь, создатели D и Rust позиционируют эти языки пригодными для написания ядра операционной системы. Вы серьезно, ребята? А вот всю эту “красоту”, мегабайты вспомогательного кода языка, с кучей системных вызовов Window/Linux вы тоже предлагаете затянуть в ядро? В общем, этот класс языков не вписался в мое требование «кросплатформенность» и «стабильность» (ну и с «автоматической уборкой памяти» и «объектно-ориентированностью» не всегда все гладко, то интерфейсы забудут добавить, то вместо классов какие-то сомнительные альтернативы предлагают) т.к. они содержат очень жирную прослойку между языком и операционной системой, а качество и стабильность этого кода вызывает много вопросов. Может быть он очень хорош, я не знаю, а если нет? Все-таки JVM и Java SDK проверены годами и прекрасно трудятся на разных ОС. Поэтому все рассматриваемые далее языки в той или иной мере опираются на JVM. Теперь перейдем к списку наших участников, с небольшим описанием, а точнее с моими субъективными впечатлениями, не стоит воспринимать их слишком серьезно.

C# (MSVC2015) — вошел в обзор только потому, что мне довелось написать на нем немалое количество строк, и никаких преимуществ по сравнению с Java я в нем не обнаружил. Скорее наоборот. К тому же с кросплатформенностью тут все не слишком радужно, родная для него платформа Windows, а mono не дает полноценной замены на Linux (см. ниже). Но мне было интересно сравнить его с точки зрения производительности с остальными участниками.

Qt/C++ (5.10) — так же был добавлен исключительно для сравнения производительности.

Ceylon (1.3.3) – этот замечательный язык был создан в 2011 Гавином Кингом, автором известного фреймворка для Java Hibernate и поддерживается сейчас сообществом RedHat. Язык очень красив, продуман, и выразителен. Наверное, так бы выглядела Java если бы развивалась без гнета обратной совместимости. Язык включает в себя все мои вышеперечисленные «хотелки» и даже более того. Я действительно стал поклонником этого языка и потратил больше всего времени на его изучение, к сожалению, информации по нему не очень много, а сообщество не слишком многочисленное и активное. На мой вкус язык почти идеален, но хотелось бы упомянуть и о недостатках. Есть плагины для Eclipse/IDE, они сделаны весьма хорошо, но оптимизация не на высшем уровне, короче говоря, они безбожно тормозят. Запуск тестов (есть специальный фреймворк для тестирования) может занять 7(!) секунд, причем сами тесты выполняются мгновенно, но старт очень неторопливый.

Скорее всего, это связано со вторым недостатком – вшитую в язык систему модульности, нет, я не хочу сказать, что модульность сама по себе это недостаток, я лишь имел ввиду, что встроенные в язык конкретные вещи становятся безальтернативными и это плохо. Так вот в Цейлоне модули имеют свой формат (.car файлы), а основано это все на системе модулей JBoss. Еще создатели языка почему-то отказались от 'protected', что не дает возможность реализовать, например, такой шаблон проектирования как «Шаблонный метод».

Scala (2.12) – такое впечатление, что создатели языка руководствовались, в основном, принципом «а давайте сделаем все ни как у людей». Вместо привычной * сделали _, а зачем? Видимо, «чтобы никто не догадался» (с). Все знают, что массив это обычно [], так давайте сделаем () и пусть им вынесет мозг. А что же тогда будет значить []? А давайте это будут дженерики? Давайте… И так далее. Чтобы вы думали значит a < — b? Я было подумал, ну небось, клонирование, или запихнуть все из коллекции b в коллекцию a, ан нет, это обход коллекции…

А почему? Да потому, что это Scala, потому что они с Тау Кита, кровь у них фиолетовая и мозги работают по-другому, короче они иные… Взяли и вшили в язык XML… Нужно, видимо, пойти дальше и встроить прям в язык Microsoft Office, почему нет? Строго внести в стандарт языка формат xls файлов… По началу, все это вызывает раздражение (еще и дурацкие названия стандартных классов – StringOps как вам?), кто-то плюнет и скажет, а ну ее эту скалу. А адепты презрительно пожмут плечами, мол, нехай сиволапый лезть в наши микросхемы, раз не понимаешь ни хрена, мы тут элита. Ну а если серьезно, обычные язык, без откровений, к сожалению, нет защиты от null pointer, только optional. Есть скользкие места и ловушки для новичков, я не уверен, что это хорошее решение для коллективного творчества.

Kotlin (1.2) – напомнил мне гнездо сороки: тут серебряная ложечка, тут фантик от конфетки… Девизом ребят, видимо, была песня «я тебя слепила из того что было». Взять из других языков проверенные временем решения и соединить, почему нет? Создатели языка называют это прагматичным подходом. Только иногда, как мне кажется, их подводил недостаток вкуса что ли (или чувства «прекрасного», на секундочку). Глядя на такую дичь как, return@forEach (прям e-mail какой-то) хочется выругаться интеллигентно и дать зарок не пользоваться этим. Но в целом язык мне все-таки понравился, лаконичный, все, что нужно есть. Жаль только присваивание в выражениях запретили и базовые типы не почистили как в Ceylon-е (вероятно, для меньших усилий при стыковке с Явой).

Fantom (1.0.69) – это такая «вещь в себе», фантомная, вроде бы уже существует с 2005 года, а вроде как и нет его. Взял в оборот исключительно из-за байки будто бы он рвет «как Тузик грелку» по производительности Яву на ее же JVM-е, ну и вообще, интересно было взглянуть на язык который компилируется в некие самопальные fcode, которые потом могут транслироваться как на JVM так и на CLR. Что сказать про сам язык? После того как я прочитал, что дженерики «пока еще не поддерживается», но это «пока» длится с каких-то там мохнатых годов и по-видимому так и останется, мой практический интерес к этому языку угас. Если быть точным поддержка дженериков там есть, но только для встроенных коллекций, а этого для инструмента разработки промышленного уровня, на мой взгляд, недостаточно. С одной стороны вроде бы есть nullable types – “Str? str”, но компилятор не препятствует обращению к str без проверки на null. Обидно…

У меня не было времени, чтобы детально исследовать/анализировать/тестировать каждый аспект языка отдельно, поэтому было принято решение написать на всех исследуемых языках, похожий код с тем же набором классов/логики и чтобы обязательно было задействовано:

• Интенсивный I/O
• Парсинг строк
• Вычисления с плавающей точкой с использованием математических функций: sin, cos, atan2, toRadians, sqrt
• Работа с коллекциями: ассоциативными массивами и просто массивами
• Лямбды (если есть)

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

Самый лаконичный код получился на Kotlin, самый изящный и доступный для быстрого понимания на Ceylon.

Немного скучных цифр, объем кода в строках/килобайтах:

• Ceylon: 128/4.7
• C#: 177/6.2
• Fantom: 153/3.8
• Java: 203/6.1
• Kotlin: 117/3.9
• Qt/C++: 413/8.3
• Scala: 123/3.6

Объем получившихся бинарных (.class) файлов в килобайтах:

• Ceylon (*.class): 31.1
• C# (.exe): 8.7
• Fantom (.pod): 7.5
• Java (*.class): 9.9
• Kotlin (*.class): 20.9
• Qt/C++ (*.exe Release): 37.7
• Scala (*.class): 20.7

Ну а теперь самое вкусное, результаты забега… Кто прибежал первым, а кто оказался аутсайдером? Делайте ваши ставки, господа! Лично мои предсказания не оправдались, а результаты удивили. Тестирование проводилось на одной машине, но на двух платформах: Windows 7 x64 Professional и Linux Debian x64 Stretch Stable. И там и там была установлена Orcale Java 8.151. На Linux скомпилированный .exe файл C# запускался через mono из стандартного репозитория Debian. Запускалось все в одинаковых условиях на незагруженной системе. Я провел два раунда, в первом на входе был только один относительно небольшой файл 30Мб, в этом случае от запуска к запуску был некоторый разброс результатов, поэтому запускал по 10 раз каждого участника и брал лучший результат. Во втором раунде условия резко ужесточились, на входе было несколько файлов общим объемом около 900Мб. В этом случае разброс был значительно меньше, видимо из-за «прогретости» JVM-а. Но для чистоты эксперимента все-таки запускал несколько раз:

Windows 7 x64 Professional

Один файл (30Мб)

• Java: 609ms
• Scala: 901ms
• Kotlin: 961ms
• C# (MSVC2015, Relese): 1108ms
• Ceylon (JavaLibs): 1209ms
• Qt/C++ (MSVC2015 64-Bit, Release): 1329ms
• Fantom: 1352ms
• Ceylon: 1538ms

Несколько файлов (~900Мб)

• Java: 28103ms
• Scala: 30837ms
• C#: 32160ms
• Kotlin: 33580ms
• Fantom: 37063ms
• Qt/C++: 39058ms
• Ceylon: тест провален, участник не смог добежать до финиша и был принудительно снят с пробега по прошествии 5 минут работы над крупным файлом 738Мб (см. комментарий ниже).
• Ceylon (JavaLibs): 41392ms

Debian x64 Stretch Stable:

Один файл (30Мб)

• Java: 667ms
• Scala: 839ms
• Kotlin: 934ms
• Qt/C++: 1047ms
• Ceylon (JavaLibs): 1171ms
• Ceylon: 1323ms
• Fantom: 1332 ms
• C# (mono, Release): 2116ms

Несколько файлов (~900Мб)

• Java: 21380ms
• Scala: 25384ms
• Kotlin: 28860ms
• Qt/C++: 30823ms
• Ceylon (JavaLibs): 37485ms
• Fantom: 39872ms
• C# (mono, Release): 65463ms
• Ceylon: тест провален, участник испустил дух не добежав до финала с таким эпикризом: «Exception in thread «main» java.lang.OutOfMemoryError: GC overhead limit exceeded»

К сожалению, так понравившийся мне Ceylon (песня ты моя не спетая), еле-еле приплелся последним, отставание от Java существенное. Scala гнал ноздря в ноздрю с Java. Fantom естественно не произвел никаких революций. Java безоговорочный лидер по скорости, причем с существенным отрывом, а вот C# немного удивил, я думал, будет где-то наравне с Java. Совсем не ожидал, что нативный код окажется в хвосте! Можно, конечно, списать на неторопливость Qt, но все же… Не, ну можно, конечно, переписать на std либах или вообще на чистый C с ассемблерным вставками, но это уже будет совсем другой код. Была надежда, что молодой да ранний Kotlin покажет близкую с Java производительность, но отставание все же есть, значительно меньше, чем Ceylon, но больше чем Scala. Причем как-то непонятно где в Kotlin может проседать перформанс, ведь у него нет своего ввода/вывода, математической библиотеки и коллекций, все это используется из Java. Вот, например, Ceylon подвели самодельные коллекции, как выяснилось, когда я стал разбираться в причинах его провала на большом объеме входных данных. Они оказались слишком прожорливы к памяти, и съели 8Гб оперативки даже не заметив, поэтому тест и «подвис», например, при запуске на машине с 24Гб памяти, тест на Ceylon проходит нормально, но, конечно, абсолютные цифры сравнивать уже нельзя, это другое железо. Мне все же удалось заставить Ceylon работать, когда я заменил коллекции из его стандартной библиотеки на коллекции из Java (HashMap и ArrayList) и тест был пройден. Так же добавил результат для Ceylon когда используются Java библиотеки для I/O и коллекция, результаты немного лучше, но не спасают ситуацию.

Кстати, попутно можно отметить, что на одном и том же железе Debian обеспечивает лучшую производительность, исходные данные я подкладывал ему на ext4 раздел, а не брал их с NTFS.

Выводы

Как я уже говорил, из всех участников мне больше всего понравился Ceylon, на нем мне действительно захотелось покодить. Но, похоже, что он еще не созрел для промышленного использования, создателям нужно всерьез озаботиться профилированием и подчистить узкие места, тем не менее, я буду следить за его судьбой. Из оставшихся вариантов я бы выбирал между Scala и Kotlin, они, кстати, синтаксически весьма схожи, тот же а-ля паскальный стиль объявления функций и переменных. Scala лучше оптимизирован по скорости и более стабилен, но зато в Kotlin есть «умное приведение» и null-безопасность. Я, пожалуй, пока остановлюсь на Kotlin и буду изучать его детальнее, возможно, следующий внутренний проект мы напишем на нем.

P.S. Да, да, я предвидел «код в студию» J
Вот исходники: drive.google.com/open?id=1N3sEsw4MZ33GI-PPQOw_vocahGdjg9bq
Вот тулза mp2dcf, которая конвертирует карты в «польском» формате в DCF: drive.google.com/open?id=12SlixUmpnrKH5Eh8k69m_T1tmWCVy6Ko
Карты можно скачать например тут: navitel.osm.rambler.ru
Я успел попробовать на Италии: navitel.osm.rambler.ru/?country=Italy

Выложил исходники на GitHub: github.com/akornilov/LangBenchmark

Комментарии (123)


  1. igormich88
    19.12.2017 18:59

    Базовые сущности: Integer и Float вот и все, что должно быть, детали пусть рулит компилятор.
    Вы предлагаете компилятору всегда использовать максимальный тип (long и double в случае Java) или вычислять необходимый размер анализируя код? В первом случае у нас будет неэффективное использование памяти и просадка по производительности (особенно на 32х битных системах), а во втором риск переполнения и/или увеличение времени компиляции.


    1. akornilov Автор
      19.12.2017 19:11

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


      1. sshikov
        19.12.2017 20:43

        в рантайме это вполне возможно

        Покажите хоть один язык, где это возможно и при этом эффективно?


        Проблема в том, что числа не одинаковые. И плавающая точка ни разу не замена целым числам. И число произвольной точности практически не реально реализовать так, чтобы это было также эффективно, как числа, укладывающиеся скажем в 128 бит. Не можете вы этим не загружать мозг.


        1. akornilov Автор
          20.12.2017 00:35

          Я не имел ввиду классы которые считают с произвольной точностью типа Decimal, пусть они себе живут отдельно, но написать класс заменяющий float и double вполне здравая идея и, например, в Ceylon так и сделали: ceylon-lang.org/documentation/1.3/tour/language-module/#numeric_types

          Насчет эффективности, я специально запрофилировал getDistance в моих тестах, где происходят самые интенсивные вычисления с плавающей точкой и Ceylon показал результат лучше чем Kotlin, но хуже чем Java.
          Мое мнение что если такой класс реализован достаточно эффективно в 99% случаев можно не парится и использовать его. Но, конечно, если на финальной стадии при профилировании обнаруживаются узкие места, тогда уже есть смысл начать что-то оптимизировать.


      1. Hazactam
        20.12.2017 11:09

        Из-за таких как ты, мы сейчас и имеем тормозное вирзо вместо половины компьютеров. Мозг нагружать? Не, это слишком сложно.


        1. akornilov Автор
          20.12.2017 12:24

          Не хами, для начала. А мозг нужно включать там где это действительно нужно.


          1. asdf87
            21.12.2017 14:40
            +1

            Ты можешь, конечно, мне не поверить, но при сравнении производительности действительно нужно нагружать мозг. Хотя бы для того чтобы понимать, что ты измеряешь то, что хотел, а не то что получилось. И что вся эта затея вообще имеет хоть какой-то
            практический смысл.


            1. akornilov Автор
              21.12.2017 15:24
              -2

              Я, конечно, дико извиняюсь, но по-моему снова «порожняк гоняем». Бла-бла-бла…
              Я то как раз прекрасно понимаю что я измеряю и как, если ты чего-то не понял из статьи — спрашивай, начнем нагружать тебе мозг :)
              Если хочешь предложить что-то конкретное, предлагай — рассмотрим.

              P.S. с теми кто обращается на Вы я тоже на Вы, с теми кто на ты, тоже на ты, так что без обид :)


              1. asdf87
                21.12.2017 18:41
                +2

                Хорошо, если действительно представляешь, что ты измеряешь. Тогда ответь на такой простой вопрос: какой смысл в том чтобы сравнивать буферизированное чтение в Java версии с небуферизированным чтением в C# версии?


                1. akornilov Автор
                  22.12.2017 01:13

                  А кто тебе сказал что StreamReader в C# не буферизирован?
                  Смотри например: msdn.microsoft.com/en-us/en-en/library/system.io.streamreader.discardbuffereddata(v=vs.110).aspx


                  1. asdf87
                    22.12.2017 15:48

                    Ты придуриваешься или правда такой? По-твоему буфер в 128 и 4096 байт одно и то же? Самое смешное, что тут даже голову особо включать не нужно было. Достаточно было загуглить и добавить BufferedStream.


                    1. akornilov Автор
                      22.12.2017 17:39

                      Спокойнее, батенька, зачем же так горячиться? :)

                      Во-первых, я не ставил перед собой целью исследовать исходники библиотек ввода-вывод и сравнивать сколько они там байт себе под буфера выделяют.
                      Во-вторых, первичная буферизация при чтении все равно происходит в драйверах/системе, а вторичная будь то 128 или 4096 байт может не дать существенного эффекта (как и показал эксперимент).
                      Не совсем в тему, но помню, что, например, игра с флагами FILE_FLAG_RANDOM_ACCESS и FILE_FLAG_SEQUENTIAL_SCAN для нативного Windows API CreateFile оказывают значительное влияние на скорость чтения.

                      Добавил BufferedStream в C# реализацию и проверил на небольших файлах, погоды он не делает, разница 10-15ms.
                      Но все равно добавлю в код, спасибо за совет :)


  1. Free_ze
    19.12.2017 19:00

    Какой смысл выкладывать цифры без кода и данных?


  1. nomoreload
    19.12.2017 19:01

    Помимо mono для C# существует ещё и .net core — который, во-первых открытый, во-вторых кросс-платформенный. Если не затруднит — прогоните ваш бенчмарк и на нём.


    1. akornilov Автор
      20.12.2017 00:36

      Не знал. Постараюсь проверить.


  1. dolgov_vv
    19.12.2017 19:11

    Хорошо бы увидеть исходный код тестовых программ (иногда, особенности применения операторов могут кратно менять скорость работы), а также используемые режимы компиляции. Без исходного кода можно только «на слово» верить циферкам в статье. Т.е. лучше не верить.

    P.S. Вы сами признаетесь, что являетесь профессионалом в Java. Написание кода на языках, которые вы знаете не так хорошо, может содержать неэффективные решения. Особенно меня настораживает проигрыш в 1.5-2 раза кода на С++.


    1. akornilov Автор
      19.12.2017 19:23

      Код выложил (см. ниже). Насчет C++ сам в шоке :)


      1. igormich88
        20.12.2017 13:34

        Насколько я помню был пост про то что на некоторых задачах Java быстрее C++ за счет оптимизаций в рантайме, но там разница была довольно несущественной (вроде бы меньше 10%).


    1. Hazactam
      20.12.2017 11:27

      Да, проблема в том, что часто ламаки берутся меряться пиписьками. Плюсы проиграли 1.5-2 раза? Не смешите мои тапки. Нативные языки (Delphi, C++) не могут проиграть виртуалкам просто физически. В идеальном случае идеальной виртуалки — пойдут вровень.


      1. akornilov Автор
        20.12.2017 12:52
        -1

        Не знаю с кем Вы там меряетесь, не смею мешать.
        Просто в JVM ввод-вывод лучше оптимизирован чем в Qt, вот и все — никаких чудес.

        P.S. ты давай там завязывай с тапкапи общаться, так и до дурки недалеко :)


  1. akornilov Автор
    19.12.2017 19:17

    Да, да, я предвидел «код в студию» :) нужно просто было срочно почистить его от проприетарных данных и дать возможность потестить на каких-нибудь свободных картах:
    Вот исходники: drive.google.com/open?id=1N3sEsw4MZ33GI-PPQOw_vocahGdjg9bq
    Вот тулза mp2dcf (набросал на Kotlin за час :) ), которая конвертирует карты в «польском» формате в DCF: drive.google.com/open?id=12SlixUmpnrKH5Eh8k69m_T1tmWCVy6Ko
    Карты можно скачать например тут: navitel.osm.rambler.ru
    Я успел попробовать на Италии: navitel.osm.rambler.ru/?country=Italy


    1. argentumbolo
      19.12.2017 19:36

      Если ищется производительность, то почему не что-то наподобие:

      QMAKE_CFLAGS_RELEASE = -O3 -march=native -mtune=native

      для Qt?


      1. akornilov Автор
        19.12.2017 19:40

        Давайте попробуем, но, на мой взгляд, выбирая профиль Release я уже и так явно выразил свои намерения и «подписался» под производительностью. А если пользователю после этого нужно все-таки лезть в настройки и прописывать какие-то дополнительные ключи, это недоработка среды/билдсистемы.
        Я вообще старался не пользоваться какими-то специфичными трюками конкретного языка чтобы не создавать ему преференций.


        1. argentumbolo
          19.12.2017 19:44

          выбирая профиль Release я уже и так явно выразил свои намерения и «подписался» под производительностью

          Нет. Вы подписались под некоторой минимально-безопасной степенью оптимизации.

          А если пользователю после этого нужно все-таки лезть в настройки и прописывать какие-то дополнительные ключи, это недоработка среды/билдсистемы.

          Снова нет. Это гибкость, которая нужна только тому, кому она на самом деле нужна.


          1. akornilov Автор
            19.12.2017 20:03

            Хорошо, не будем спорить о частностях здесь. В контексте статьи, я не «ищу производительность», я сравниваю производительность языков в их дефолтной конфигурации. Для Qt это Release.
            Взять хотя бы "-march=native -mtune=native" — это уже нарушение одинаковых условий для всех, ведь JVM не был скомпилирован конкретно для моей машины.


            1. argentumbolo
              19.12.2017 22:40

              я сравниваю производительность языков в их дефолтной конфигурации. Для Qt это Release.
              Взять хотя бы "-march=native -mtune=native" — это уже нарушение одинаковых условий для всех, ведь JVM не был скомпилирован конкретно для моей машины.

              Абсолютно некорректное сравнение.
              Неважно под какую машину была скомпилирована Java, всё равно её JIT-компилятор скомпилирует ваш код под конкретно ваш компьютер (если вы прямо не запретите ей это ключами).
              C++ же язык статический, он применяет оптимизации не в процессе работы программы, а в процессе компиляции.

              Так что сейчас вы установили, что для очень простых программ максимальная оптимизация HotSpot работает лучше чем средняя C++ компиллятора.
              Что и так очевидно.

              Так как С++ компилятор используется в самых различных вариантах оптимизации (от дебага, до полного инлайна всех функций с разворотом всех циклов), то как дефолтный принят некий средний уровень.
              И если уж вы пользуетесь этим компилятором, то и настраивайте его соответственно.


              1. akornilov Автор
                20.12.2017 00:42

                Я рад что работа JIT подсистемы для вас очевидна. Но мне не хотелось «думать» за JVM, как она там будет оптимизировать в рантайме, меня не особо тревожит, это ее дело. Кстати, JIT компилятор тоже написан на C/C++ и настройки компилятора с которыми он был построен напрямую влияют на его производительность так что с корректностью здесь я не вижу никаких проблем.
                Повторюсь, компилятор настроен профилем Release — точка. И у меня нет желания прогонять его с десятками разных ключей для GCC или Microsoft Compiler-а и выбирать лучший вариант для C++. Как настроили разработчики — так и поехало.
                Предлагаю завершить на этом дискуссию, если вам интересно с O3 — исходники в вашем распоряжении, проверяйте, делитесь результатами.


                1. argentumbolo
                  20.12.2017 01:46

                  Повторюсь, компилятор настроен профилем Release — точка.

                  Ваше право.
                  Только статья тогда должна называться: «Проверка дефолтных настроек различных языков, технологий, компиляторов и реализаций потоков данных».


                  1. akornilov Автор
                    20.12.2017 12:26

                    Думаю что не стоит выносить это в заголовок, это будет понятно из контекста по мере прочтения.
                    Но все равно спасибо за Ваше мнение, я, если честно, тоже колебался добавить O3 или нет, примерно с такими же аргументами как в нашем диалоге :)


      1. Siemargl
        19.12.2017 19:56

        Бесполезно. Потоки в С++ исторически медленные. Видимо, в Qt тоже.
        Для производительности — только прямое чтение.

        Не понял, о чем статья: JVM works well — К.О. Но за счет памяти. Еще на ней можно писать парсеры. Всё.


        1. akornilov Автор
          19.12.2017 20:08

          Сравнивалось не C++ с JVM, а производительность разных языков на JVM. То что JVM работает нормально и на нем можно писать все что угодно у меня сомнений не вызывало.


          1. Siemargl
            19.12.2017 21:07

            Здесь сравниваются не языки, но реализация библиотеки ввода-вывода. И немного Map. Всего лишь.

            Нужны другие тесты.

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


            1. akornilov Автор
              20.12.2017 00:57

              Во-первых, вы как-то забыли упомянуть арифметику с плавающей точкой и работу со строками.
              Во-вторых, для языков с JVM подсистема ввода-вывода особой роли как раз не играет, т.к. используется из Java, а результаты тем не менее существенно различаются.
              Так что здесь, всего лишь, сравниваются производительность языков на JVM и тесты на мой, естественно, субъективный взгляд вполне адекватные и показательные :)
              Кому нужны другие тесты могут проделать эту работу самостоятельно и поделиться результатом, с большим интересом почитаю :)

              P.S. на гитхаб выложу на досуге, может и правда блох наловим в коде :)


        1. akornilov Автор
          20.12.2017 01:30

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


          1. 0xd34df00d
            20.12.2017 20:47

            Делать руками mmap и string_view потом поверх него — быстрее в несколько раз.

            fscanf, правда, медленнее, но то такое.


            1. akornilov Автор
              21.12.2017 11:47
              -2

              mmap это уже завязки на платформу. Понятно, что если все сделать вручную будет быстрее, но это надо тогда в каждом языке делать. Можно в той же Java не пользоваться стандатной библиотекой I/O, а прокинуть нативный вызов и сделать тот же mmap.
              Только зачем? Мне интересно было сравнить пользуясь стандартными средствами языка, если его библиотека I/O плохо оптимизирована эту уже минус.


            1. Hokum
              21.12.2017 13:00

              Как вариант, можно воспользоваться std::filebuf — он ощутимо быстрее, чем fstream.


  1. argentumbolo
    19.12.2017 19:43

    deleted


  1. Zebradil
    19.12.2017 23:15

    Бинарь получившийся на выходе обычно измерялся мегабайтами.

    Сравниваете тёплое с мягким. *.class получается сравнительно небольшим, но для его запуска нужна отдельная программа. Бинарник на Rust получается 500Кб, что много, если не думать о том, что он сам же и обеспечивает уборку за собой.


    1. akornilov Автор
      20.12.2017 01:03

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


      1. fcoder
        20.12.2017 12:48
        +1

        По сравнению с размерами рантайма такое сравнение имеет не очень большой смысл


        1. akornilov Автор
          20.12.2017 12:54

          Ну почему же, ведь классы выполняются на одной и той же JVM.


          1. fcoder
            23.12.2017 02:30

            Ну у C# совсем другая среда исполнения. И у С++ тоже. Кроме того, даже у JVM-based языков может быть выделенный в отдельный файл рантайм. И наоборот, набор просто утилит, который по-умолчанию включен в сборку, даже если не используется. Поэтому просто по размеру сложенных в директорию файлов и их размерам сложно дать оценку «нагенеренной прослойки», а размер «полезной» части бинарника, в свою очередь напрямую зависит от того насколько богат рантайм.

            Можно относительно честно сравнить размеры компилируя всё статически (и даже здесь будет чувствоваться влияние разных ОС и архитектуры системы под которую собираем). Но даже сравнивая эти цифры, довольно сложно сделать какие-то выводы наверняка.


  1. visirok
    19.12.2017 23:44

    Я не очень понимаю, почему статья (на момент моего прочтения) так низко оценена. Человек проделал работу и поделился результатами. Понятно, что первые опыты писания программ на новом языке могут привести к неэффективному коду, который профессионал может улучшить в несколько раз. Понятно что проверялась только малая часть функциональности. И тональность слегка залихватская. Но информация в целом интересная и полезная.


    1. akornilov Автор
      20.12.2017 01:14

      Спасибо! По мне так про бенчмаки всегда интересно почитать тем более когда за тебя кто-то уже это все проделал :)
      Ну а профессиональные (и адекватные :) ) комментарии к коду только приветствуются.


    1. visirok
      20.12.2017 08:45

      И ещё одно замечание/дополнение. Всё-таки использование Optional позволяет начиная с Java 8 обходиться без null.


      1. akornilov Автор
        21.12.2017 11:52

        Верно. Но Optional мне не нравится, под защитой я имел ввиду:
        String str = null; // Ошибка компиляции, тип не поддерживает null
        — String? str = null; // OK.
        str.length() // Ошибка компиляции (не рантайма!)
        Это может отловить кучу проблем на раннем этапе.


        1. visirok
          21.12.2017 17:58
          +1

          Тут я с Вами не соглашусь.
          Задавая Optional вы одновременно указываете тип обьекта, который он может содержать. И с этого момента компилятор помогает Вам.
          Мой опыт по использованию Optional очень позитивный.
          Осмелюсь предложить Вам почитать вот эту серию статей.


          1. akornilov Автор
            22.12.2017 01:18

            Спасибо, почитаю!


    1. Free_ze
      20.12.2017 12:38
      +1

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

      Это лишает статью смысла, ведь в ней сравнивается производительность. Методика сравнения описана общими словами, а код вообще не сразу появился, а когда появился — в комментариях, в виде архива в облаке. То есть, имхо, работа сделана как попало, статья оформлена как попало.
      Плохая статья — плохие оценки, все логично. Но, кмк, это должно мотивировать автора совершенствовать навыки, а не обижаться и замыкаться в себе. Эго отращивать вредно, а на качество жизни рейтинг хабра не влияет.


      1. akornilov Автор
        20.12.2017 12:49
        -1

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


        1. Free_ze
          20.12.2017 12:59
          +1

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

          А судить о качестве статьи по тому где выложен код, это как-то… странно :)
          Согласитесь, услышать про джаву, которая в очередных синтетических тестах чудным образом обогнала натив и не найти пруфы — это не менее странно.


          1. akornilov Автор
            20.12.2017 13:10

            Я сам удивился, но думаю, что объяснение очень простое, как я уже написал выше, в JVM ввод-вывод лучше оптимизирован чем в Qt, вот и все. Тем более код есть желающие могут с ним поиграть.


            1. Free_ze
              20.12.2017 13:51

              Об этом кричат с тех пор, как для Java появился JIT-компилятор. Чтобы узнать, отличается ли ваши тесты от десятков других, нужно смотреть код, а скачивать его себе с гуглдрайва — это не удобно. Спасибо, что хоть так.


    1. stack_trace
      20.12.2017 16:16
      +1

      Информация интересная, если принять её на веру. Но все тут люди опытные, к информации относятся критически и для многих (например для меня) множество пассажей выглядят очень сомнительными, как и полученные результаты. Статьи содержащие сомнительную информацию минусят, и это правильно.


      1. visirok
        20.12.2017 22:51

        Я думаю, это вопрос перспективы. С одной стороны каждый имеет право поставить свой плюсик или минусик. Даже если их уже стоит 10, 100 или больше. Но с другой стороны, если минусиков стоит много — стоит ли ещё один добавлять? Может лучше поставить плюсики за понравившиеся критические комментарии?
        Тем более я посмотрел, это самая первая статья автора.
        Но это моё личное мнение. Я никому её не навязываю.


        1. akornilov Автор
          21.12.2017 11:59
          -1

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


          1. visirok
            21.12.2017 17:29
            -1

            Другие коммениаторы внесли немало конструктивных предложений, как мне представлчется. Моя идея очень простая — если у статьи стоит уже 10 минусов, какой смысл ставить 11,12 ...120?
            Разумеется, если нет понравившихся комментариев, то и плюсики ставить некуда. Тогда может стоить подумать что в статье не понравилось и свой комментарий написать.


            1. akornilov Автор
              22.12.2017 02:12
              -1

              Так тут, в основном, C++ перевозбудились. Призывают не пользоваться стандартным вводом, а прям системными вызовами шпарить. Мне это не интересно, и я уже написал почему.


        1. Ashot
          21.12.2017 12:31

          Может лучше поставить плюсики за понравившиеся критические комментарии?

          Для этого можно поставить плюсик конкретным критическим комментариям. А ставить оценку именно статье за комментарии в ней – ну это как минимум странно.


          1. visirok
            21.12.2017 17:30

            Из чего Вы заключили, что я призываю ставить оценку статье за комментарии к ней?


            1. Ashot
              21.12.2017 19:38

              Может лучше поставить плюсики за понравившиеся критические комментарии?

              Перечитал эту фразу. Теперь сижу и думаю, что я дебил. Я вот её расценил почему-то сначала как "поставить + статье за комментарии". Извиняюсь за свой мозг, который почему-то таким образом информацию воспринял.


              1. visirok
                21.12.2017 21:03

                Не наговаривайте на себя. К тому же — Вы не одинок. Поскольку ещё два читателя, поствившие плюсики за ваш комментарий, тоже видимо о том же подумали.


                1. Hokum
                  22.12.2017 13:53
                  +1

                  Оценка за статью ставится статье, оценка за комментарий комментарию. И не надо К статье можно отнестить «нейтрально» — не поставить оценку, можно оценить как «хорошо» и как «плохо». И это три разных оценки. А в предлагаемом Вами подходе стирается разница между неудачной публикацией и откровенно плохой. Все таки разница -10 и -20 существенна, равно как и между +10 и +20.

                  Конечно, чтобы мнение было непредвзято, было бы здорово не видеть текущую оценку, до момента «голосования» или истечения срока голосования. Мне попадалось исследование, по итогу которого был сделан вывод, что люди ставят более охотно оценку которая совпадает с большинством. Но если не показывать текущую оценку статьи это может поломать систему, по которой статьи отбираются и показываются в лучшем. Так как если ты читаешь статью из лучшего — значит у нее высокая оценка, хотя ты не знаешь, может быть все статьи за выбранный период имеют отрицательную оценку. :) Так что, может быть, сокрытие оценки и было бы полезным.


                  1. Free_ze
                    22.12.2017 15:31
                    +1

                    чтобы мнение было непредвзято, было бы здорово не видеть текущую оценку, до момента «голосования» или истечения срока голосования
                    А как же хайп вокруг сильно заплюсованных/заминусованных свежих статей?) ТМовцы не с проста спрашивали об этом не так давно.
                    Кмк, неплохо было бы показывать градиент оценок за некотороый крайний отрезок времи, а не абсолютное значение.


  1. tzlom
    20.12.2017 00:18

    Беглым взглядом находятся различия между джавовским кодом и С++
    например вместо
    mSegments.contains(segment.getClustId())
    нужно использовать find для получения идентичного поведения
    Объекты вы передаёте копиями — зачем? Можно по ссылке(как в джаве) или move. Я думаю это не все, но с телефона смотреть не удобно.


    1. akornilov Автор
      20.12.2017 01:09

      Вполне возможно, что есть небольшие расхождения, код писался в урывками в свободное от работы время, но если вы убедите меня что это может существенно повлиять на перформанс я постараюсь исправить и обновить результаты.
      Вообще, я вроде старался активно использовать константные ссылки где можно. Возможности по перемещению из C++11, действительно не использовал.


      1. stack_trace
        20.12.2017 17:14

        Да, это может существенно повлиять на результаты. Например, взять небольшой фрагмент кода из performer.cpp:


        if (mSegments.contains(segment.getClustId())) {
            mSegments[segment.getClustId()] << segment;
        } else {
            QList<DcfSegment> list;
            list << segment;
            mSegments[segment.getClustId()] = list;
        }

        Во первых, два поиска по мапу вместо одного (про это уже сказали). Но наиболее характерный код из ветки else, очень характерный для Java и ужасный для C++. По таким моментам очень легко заметить, что вы обычно пишите на Java и плохо разбираетесь в C++. Для джавы код вполне логичен — вы создаёте новый список и помещаете его в мап. Однако в C++ это будет выполняться так: вы создаёте сначала временный экземпляр класса, кладёте в него элемент, потом создаёте ещё один список внутри хэшмапа, присваиваете локальный список тому, что находится в хэшмапе, при этом по новой происходит выделение памяти в хипе и копирование всего содержимого (пусть это даже всего один элемент), после чего локальная копия уничтожается с освобождением памяти. Итого, у вас лишние поиски по хэшмапу, лишние копирования, и лишние операции с памятью, которые достаточно дорогие.


        Но важнее всего то, что здесь вообще не нужен if и ветвление. Создание нового списка происходит при вызове оператора []. Плюсовик написал бы вместо всего этого одну строчку, которая при этом и работала бы быстрее:


        mSegments[segment.getClustId()] << segment;


        1. akornilov Автор
          21.12.2017 12:04
          -1

          Ну вот уже что-то :) но при всем уважении на хипе здесь ничего создаваться не будет, все на стеке.
          Если QHash не содержит ключа он создаст объект по дефолтному конструктору, что в нашем случае собственно и нужно. Хорошо поправлю, но не думаю что это существенно повлияет на результат. Потому что я проводил подобный эксперимент на Kotlin-е, делал сначала проверку ключа, потом подкладывал — на больших тестах разница была порядка 40-50ms.


          1. Chaos_Optima
            21.12.2017 12:22
            +1

            Тут речь была не про QHash а про QList, он будет внутри создавать элементы на куче.


            1. Free_ze
              21.12.2017 12:44
              +1

              Да и в QHash, кстати, тоже в хипе.


          1. Free_ze
            21.12.2017 12:40

            на хипе здесь ничего создаваться не будет, все на стеке.
            "QList will allocate its items on the heap unless sizeof(T) <= sizeof(void*)"
            документация | сорцы


            1. akornilov Автор
              21.12.2017 12:58

              Согласен, но это детали реализации QHash и QList, я говорил об объектах которые мы явно создаем в коде.


              1. Free_ze
                21.12.2017 13:19

                Изначально неправильно используется API коллекций, об этом пишут в документации. Я лишь показываю почему там так написано.


          1. stack_trace
            22.12.2017 15:54

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


  1. Dromok
    20.12.2017 02:59

    Ваще не понял почему статью заминусовали. По-моему очень интересно. Автору респект и плюсик в карму!


    1. akornilov Автор
      20.12.2017 12:21
      -1

      Спасибо!

      Думаю тут дело в том, что выбор языка программирования это вопрос достаточно интимный, сродни выбору жены :), а при сравнении всем не угодишь, вот последователи разных языковых школ и размахались «минусовками». Ваш, коммент тоже, наверное, заминусуют :)


      1. Chaos_Optima
        20.12.2017 17:25

        Дело не в том что вы всем не угодили, а в том что это очередной нубо бенчмарк языков, в котором сравниваются даже не языки во многом а реализацию библиотек и IO. Лишь одна замена потоковго чтения файла в плюсах на чтение файла из памяти уже гарантирует огромный прирост производительности. Если в бенче java обгоняет C++ то вероятность того что код написан криво огромна (что в принципе так и есть судя по исходникам).


        1. akornilov Автор
          21.12.2017 12:10
          -1

          В обзоре не было реализации на чистом C++, везде четко было прописано Qt/C++. Кроме голословных утверждений о «кривизне» кода дело не идет.


          1. Chaos_Optima
            21.12.2017 12:34

            А они не голословные выше вам уже показали пример вашей кривизны, а также вы в упор не видите что потоковое чтение из файла в С++, никогда не было быстрым, и тут не важно чистый С++ или Qt\C++ вы написали не оптимальный код и при этом говорите что в проседании скорости виноват язык, дело конечно ваше во что верить, но и удивляться тому что вас минусуют не стоит, как и прикрываться тем что вы «не угодили».


            1. akornilov Автор
              21.12.2017 12:53
              -1

              Насчет «не угодил», это, конечно же, была шутка, а прикрываться мне не от чего собственно.
              «Вашей кривизны» звучит как-то двусмысленно, будем считать, что это относится к коду :) я так же согласился что это косяк, но боюсь, что существенно на производительность это не повлияет (но надо будет проверить).
              В Qt/C++ получились результаты такими какими получились, не вижу смысла там что-то менять, какая библиотека ввода — такой и результат.
              Если кто-то напишет хорошую реализация на C++ можно добавить еще и ее для сравнения. Но меня больше интересовали языки с JVM.


  1. qwert_ukg
    20.12.2017 06:44

    C# (MSVC2015) — вошел в обзор только потому, что мне довелось написать на нем немалое количество строк, и никаких преимуществ по сравнению с Java я в нем не обнаружил.

    Вы видимо хеловорды писали.


    В сишарпе есть:


    • свойства
    • нулабилити
    • екстеншн функции
    • async/await
    • имутабельность
    • удобная вариантность <in/out T>

    Интересно, как Вы сравнивали?


    1. qwert_ukg
      20.12.2017 07:20

      Забыл про вывод типов


      1. akornilov Автор
        20.12.2017 14:11

        Тоже хорошая штука.


    1. akornilov Автор
      20.12.2017 13:15

      Я его особо не сравнивал, он у меня отсеялся на стадии «кросплатформенность», хотя вон тут пишут про core.net, надо будет попробовать. Как я уже написал в статье мне он был больше интересен в аспекте производительности.
      Из перечисленного Вами списка кое-чем пользовался: свойства, async/await, вариативностью. Еще там достаточно удобно форматировать строки $"".


      1. qwert_ukg
        20.12.2017 13:47

        Вы пишите:


        никаких преимуществ по сравнению с Java я в нем не обнаружил.

        и тут же, в коментарии, вы пишите:


        кое-чем пользовался: свойства, async/await, вариативностью.

        отсюда вопрос — где правда? Как можно пользоваться свойствами и не заметить этого?


        1. akornilov Автор
          20.12.2017 14:10

          Ну разве я где-то написал что не заметил свойства?
          Правда в том, что считать преимуществом, и это уже достаточно субъективно. Безусловно в C# есть свои приятные плюшки, но я не занимался их детальным сравнением в статье, а написал свои общие впечатления о C# в сравнении с Java. Ведь, как известно, на вкус и цвет товарищей нет, кому-то нравится C#, кому-то Java, кому-то Basic…


          1. qwert_ukg
            20.12.2017 14:20

            Ну Вы же пишите что:


            никаких преимуществ по сравнению с Java я в нем не обнаружил

            Я вам написал целый список преимуществ в сравнении с джава, вы говорите — да ими пользовался — я не понимаю логики. Вы не считаете это преимуществами?


            1. akornilov Автор
              20.12.2017 14:26
              -1

              Хорошо, давайте так, я для себя вынес взвешенную оценку C# и в результате сумма его преимуществ и недостатков оказалось ближе к нулю или чуток отрицательной. Т.е. недостатки для меня перевесили преимущества.
              Согласитесь что ваш список это тоже не абсолютная истина? Не все поголовно запишут это в преимущества. Кто-то скажет — свойства это круто! Кто-то нет. В той же Java можно реализовать часть этих фич через фреймворки если нужно.


              1. qwert_ukg
                20.12.2017 14:31

                Кто-то скажет — свойства это круто! Кто-то нет

                Ну теперь мне все понятно :)


                1. akornilov Автор
                  22.12.2017 02:05
                  -1

                  Ну вот вам, например, мнение:
                  «Лично мне свойства не нравятся, и я был бы рад, если бы их поддержку убрали из Microsoft .NET Framework и сопутствующих языков программирования. Причина в том, что свойства выглядят как поля, на самом деле являясь методами.»
                  Джеффри Рихтер
                  ru.wikipedia.org/wiki/%D0%A1%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5_C_Sharp_%D0%B8_Java#%D0%A1%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%B0

                  Как по мне так, например, такой достаточно типовой пример использования свойства в C#:
                  public string Foo { get; set;}
                  На Java можно записать просто
                  public String foo;
                  без накладных расходов на get; set; методы.

                  Если же мы решили запихнуть в get и set какую-то более сложную логику, то нам все-равно придется добавлять приватную переменную и тогда Java пара getter/setter будет не намного хуже.


                  1. qwert_ukg
                    22.12.2017 05:46

                    Я не знаю что на это ответить. Очевидно что у вас не хватает академической базы. Учитесь.


                    1. akornilov Автор
                      22.12.2017 12:19
                      -3

                      Ну раз в ход пошла «академическая база» :) думаю не ошибусь если предположу что речь про:
                      public String foo;

                      Да, да, всех нас учили что нельзя шарить поле класса, только через методы доступа.
                      А вы когда-нибудь задумывались почему так нужно делать?
                      Основной аргумент как раз «академический», дескать нарушение инкапсуляции данных.
                      А что разве тупые геттеры/сеттеры, которые только возвращают и только присваивают его не нарушают?
                      В конце концов всем надоел этот шаблонный совершенно бессмысленный код и придумали свойства, чтобы это выглядело похожим просто на переменную.
                      И тут я не могу не признать резонность довода Джеффри Рихтер. Смотришь в код, на первый взгляд вроде бы выглядит как обычное присваивание, а потом идешь в сеттер свойства и мать чесная, там «Война и мир», кода вагон и маленькая тележка.
                      В более современных языках придумали «абстрактные атрибуты»:
                      Kotlin, Scala:
                      val foo: String
                      Да, здесь компилятором будут сгенерированы get и set, но по мне это ничем не хуже дедовского:
                      public String foo;

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


                      1. Free_ze
                        22.12.2017 12:30
                        +2

                        нарушение инкапсуляции данных. А что разве тупые геттеры/сеттеры, которые только возвращают и только присваивают его не нарушают?

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

                        Это всё выливается в то, что при изменении логики аксессоров даже не нужно перекомпилировать клиентские сборки, не говоря уже о необходимости грепать по исходникам клиента, чтобы воткнуть обёртку в каждом месте использования.

                        без накладных расходов на get; set; методы.
                        Методы могут инлайниться компилятором.


                        1. akornilov Автор
                          22.12.2017 12:50
                          -1

                          Если нужен абстрактный атрибут или предполагается переопределение в наследниках, тогда да, не вариант.


                      1. qwert_ukg
                        22.12.2017 13:07

                        В более современных языках придумали «абстрактные атрибуты»:
                        Kotlin, Scala:
                        val foo: String
                        Да, здесь компилятором будут сгенерированы get и set, но по мне это ничем не хуже дедовского:
                        public String foo;

                        хуже, там приватное поле, и аксессоры (в вашем примере только геттер)


                        1. akornilov Автор
                          22.12.2017 13:15
                          -1

                          очепятка :)
                          var foo: String

                          Если высока вероятность что в будущем придется изменить логику геттеров-сеттеров (т.е. отказаться от сгенерированного компилятором кода и явно написать код для get/set), тогда действительно хуже.


  1. AlexPublic
    20.12.2017 08:50

    Мда… Мне ещё не приходилось писать столько отрицательных комментариев, к статье с вроде как правильной изначальной идеей — такого уникально низкого уровня реализации я давно не видел.

    Для начала сразу же повеселила фраза «Т.е. маленькая програмулька, которая печатает в консоль тупую строчку и должна занимать от силы 3-4Кб весит мегабайты!» Ну так оно и весит мегабайты на практически всех языках, кроме разве что чистого C++. В той же Java это будет более 100 мегабайт (мы же будем по честному считать, включая рантайм, не так ли?). Причём эта фраза была в статье не просто так, а на её основание из тестирование убрали как раз самые интересные и перспективные языки.

    На фоне предыдущего пункта меня сильно поразило наличие в тестирование C++/Qt, т.к. как раз Qt и является примером очень жирной библиотеки — тут было явно что-то не то. Но когда я увидел размер программы на Qt в 37 КБ, то всё стало на свои слова — автор похоже ещё и не особо разбирается в разнице между статической и динамической линковкой…

    Так же слегка шокировали слова «обычные язык, без откровений» по отношению к Scala. И это про язык, который принёс полноценное функциональное программирование и метапрограммирование в мир JVM.

    После всего этого на результаты тестов уже можно было и не смотреть особо… Гораздо интереснее было взглянуть на исходники. И в первую очередь на вариант с C++. Собственно как и ожидалось, там был классический «java стиль», тот самый, который не позволяет писать быстродействующее ПО на C++. Если же решить эту простейшую задачку на нормальном современном C++ (и для начала это будет Boost, а не Qt), то она будет и в разы быстрее и в разы меньше кода.

    В итоге статье крайне не понравилась огромным числом ляпов, не говоря уже о смешных итоговых результатах.


    1. acmnu
      20.12.2017 10:37

      Посмотреть бы в глаза тому человеку, который эту статью заапрувил.


      1. Hazactam
        20.12.2017 11:33

        Хабр уже не торт? :)


    1. akornilov Автор
      20.12.2017 12:41
      -1

      Мда… Мне ещё не приходилось писать столько отрицательных комментариев, к статье с вроде как правильной изначальной идеей — такого уникально низкого уровня реализации я давно не видел.

      Ну это Ваша, абсолютно субъективная точка зрения совершенно ничем не подкрепленная. На это можно ответить только что-нибудь типа «я давно не видел комментария с таким высоким уровнем предвзятости и фанатизма».

      Для начала сразу же повеселила фраза

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

      Раз уж это моя статья позвольте мне и решать кого записывать в перспективные. Напишите свою — с удовольствием почитаю.

      На фоне предыдущего пункта меня сильно поразило наличие в тестирование C++/Qt, т.к. как раз Qt и является примером очень жирной библиотеки — тут было явно что-то не то.

      На мой взгляд, красота и удобство Qt API в сравнении с убогой std в разы перевешивают возможные потери производительности.

      Но когда я увидел размер программы на Qt в 37 КБ, то всё стало на свои слова — автор похоже ещё и не особо разбирается в разнице между статической и динамической линковкой…

      А мне кажется что Вы слегка забываетесь и переходите на личности. Как я уже писал в Qt я использовал дефолтный профиль Release ничего не меняя. И Qt, естественно, использовал динамическую линковку, это нормально и правильно.

      Так же слегка шокировали слова «обычные язык, без откровений» по отношению к Scala. И это про язык, который принёс полноценное функциональное программирование и метапрограммирование в мир JVM.

      За функциональное программирование в статье ничего не было. Мне как-то Haskell-а вполне хватает.

      Собственно как и ожидалось, там был классический «java стиль»

      Да нет там никакого «Java стиля» обычный Qt-шный код.

      Если же решить эту простейшую задачку на нормальном современном C++ (и для начала это будет Boost, а не Qt),

      Код в студию, пожалуйста, только не в С-style.


      1. DarkEld3r
        20.12.2017 12:58

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

        Это немного не так работает.


        Ну и мы, как аудитория, в праве писать (любые) комментарии и (негативно) оценивать статью.


        1. akornilov Автор
          20.12.2017 13:06

          Это я к тому что в статье я написал так как посчитал нужным и не собираюсь ее менять, а в коментах само собой можно пообщаться.


      1. AlexPublic
        21.12.2017 09:37

        Во-первых, JVM бывают разные, есть очень маленькие.

        Меньше получаемого в Rust/D/Nim исполняемого файла под той же платформой? )

        В-третьих, JVM портирован почти везде, так что можно считать его частью системы.

        Похоже кто-то тут путает кроссплатформенность (имеется не только у JVM, а много где, включая и Rust/D/Nim) и предустановленный в данной ОС рантайм. Я что-то не слышал про предустановленный рантайм Java на Windows или OSX или Linux…

        Раз уж это моя статья позвольте мне и решать кого записывать в перспективные. Напишите свою — с удовольствием почитаю.

        Выбирать критерии отбора языков — это безусловно авторское дело. Однако, даже если использовать для этого такой странный по нынешним временам (если конечно речь не про МК) параметр, как размер получаемого дистрибутива, то всё равно это надо делать честно (учитывая весь рантайм). Именно это один из главных недостатков статьи, просто бросающийся в глаза своей некорректностью.

        На мой взгляд, красота и удобство Qt API в сравнении с убогой std в разы перевешивают возможные потери производительности.

        Ха, совсем неудивительна симпатия Java-программиста к Qt. Только вот надо понимать, что это совсем не путь развития современного C++. Qt родилась в 90-ые (как раз одновременно с Java) и вся её архитектура из тех времён. Сейчас мир C++ уже далеко ушёл от этого в совсем другую сторону и данный Java-стиль является откровенным legacy. Конечно авторы Qt понемногу пытаются исправить эту ситуацию, но пока выходит очень слабо.

        Да нет там никакого «Java стиля» обычный Qt-шный код.

        )))

        Код в студию, пожалуйста, только не в С-style.

        Накидал сейчас для развлечения на нормальном C++ (с Boost'ом вместо Qt) программку с полностью аналогичной функциональностью. Значит получился один файл исходников из 70 строк/2,4 КБ, причём 1/3 файла занимают подключения заголовочных файлов и пространств имён. Исполняемый файл при полной (включая стандартную библиотеку языка, т.е. такой exe можно спокойно распространять везде в одиночку) статической линковке на 64-ёх битной Windows занимает 1 МБ. Время работы в 1,5 раза меньше, чем у приведённого в статье Java кода, что в общем то и ожидалось изначально.


        1. qwert_ukg
          21.12.2017 09:48

          Жаль akornilov вам не сможет ответить. А вы не смотрели примеры для котлина?


          1. akornilov Автор
            21.12.2017 12:17
            -1

            Да почему же не смогу? :) Отвечаю.
            Отрадно что человек сел и написал код, все лучше чем слюной брызгать :)
            Ежели он его еще и опубликует и он окажется адекватным, я добавлю его в тесты и будет у нас полный комплект: современный C++ :)

            P.S. Вероятно, товарищ начитался Скотта Майерса, раз уж про «современность» древнего C++ заговорил :)


            1. DarkEld3r
              21.12.2017 12:35

              «современность» древнего C++

              Это о чём?


            1. Chaos_Optima
              21.12.2017 12:38
              +2

              Новые стандарты выходят каждые 3 года и использование новых стандартов и значит писать на современном С++.
              Ваш кэп.


          1. AlexPublic
            21.12.2017 20:20

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

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


        1. vlanko
          21.12.2017 09:59

          Раньше в MacOS точно был встроенный рантайм Java, в новых возможно есть.


          1. akornilov Автор
            21.12.2017 12:23

            Да и в Линухе open-jdk, обычно, при установке в качестве зависимостей затягивается.


        1. Free_ze
          21.12.2017 11:13
          +1

          Значит получился один файл исходников из 70 строк/2,4 КБ, причём 1/3 файла занимают подключения заголовочных файлов и пространств имён.

          Зачем описывать словами, когда можно просто показать код?


          1. AlexPublic
            21.12.2017 20:21

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


            1. Free_ze
              21.12.2017 21:16

              Но код вы не покажете?) Интересно было бы сравнить подходы с авторскими.


              1. AlexPublic
                21.12.2017 23:16

                См. конец этого habrahabr.ru/post/345100/#comment_10580696 комментария. Правда хабр слегка испортил там переносы строк, но думаю можно разобраться. )))


        1. akornilov Автор
          21.12.2017 13:31

          Меньше получаемого в Rust/D/Nim исполняемого файла под той же платформой? )

          Исполняемый файл будет больше чем jar, но зато в нем будут все «кишки» языка :) если прибавить рантайм Java то он будет весить больше.
          Вам самому-то не надоело это муслоить? :) Вроде бы очевидные вещи.
          Совершенно не хочется пересказывать ту часть статьи другими словами — кратко «жирные» бинари на мой взгляд это не эстетично, думаю тут есть простор для оптимизации, не более того. А вышеупомянутые языки отсеялись по многим другим факторам, в частности:

          Похоже кто-то тут путает кроссплатформенность (имеется не только у JVM, а много где, включая и Rust/D/Nim) и предустановленный в данной ОС рантайм.


          А похоже кто-то очень любит поумничать :) ну да ладно все мы тут не без греха :)
          Думаю что JVM портирован на большее количество платформ чем тот же Nim и Rust — это кроссплатформенность. Да и со стабильностью в JVM все будет почетче.

          Сейчас мир C++ уже далеко ушёл от этого в совсем другую сторону и данный Java-стиль является откровенным legacy

          Спорное утверждение, дизайн проверенный временем доказал свое право на существование.

          Накидал сейчас для развлечения на нормальном C++

          Отлично, код в студию! :)


          1. AlexPublic
            21.12.2017 23:11
            +2

            Исполняемый файл будет больше чем jar, но зато в нем будут все «кишки» языка :) если прибавить рантайм Java то он будет весить больше.
            Вам самому-то не надоело это муслоить? :) Вроде бы очевидные вещи.


            Если это действительно очевидные вещи, то тогда солидный кусок обсуждаемой статьи надо бы выкинуть… )))

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

            Это уже не инженерные аргументы, а какие-то личные фобии.

            Думаю что JVM портирован на большее количество платформ чем тот же Nim и Rust — это кроссплатформенность. Да и со стабильностью в JVM все будет почетче.

            Вообще то Nim компилируется в C++ (который априори более кроссплатформенный, чем любые обсуждаемые тут языки). А что касается Rust'а, то например это второй язык (после C++), который имеет возможность написания кода для платформы WebAssembly (будущее нагруженного web'a), что является недостижимым для JVM в данный момент.

            Спорное утверждение, дизайн проверенный временем доказал свое право на существование.

            Любители Кобола говорили также… )))

            Отлично, код в студию! :)

            Сейчас будет, но перед этим хочу заметить, что из-за создания своего варианта, я более внимательно посмотрел на представленные и заметил откровенные махинации. Странно что никто больше их не заметил. А именно, у класса DcfSegment (именно его экземпляры создаются миллионами в тесте) в Java реализации имеется функция getLength(), которая вычисляет длину сегмента при своём вызове (т.е. никогда в данном тесте) на базе значений конечных точек. В тоже время аналогичная функция из класса DcfSegment в C++ реализации возвращает значение поля mLength (которого вообще нет в Java реализации), которое вычисляется в конструкторе класса. Т.е. в C++ варианте не только имеем лишнее поле, но и дополнительные тяжёлые вычисления при каждом конструирование такого объекта. И это называется тестом производительности? Скорее похоже на откровенную махинацию…

            Ну а что касается варианта на современном C++, то выкладывать весь проектик на какие-то ресурсы мне лень, так что кину исходник прямо в этом комментарии (обрезав инклуды с неймспейсами, чтобы покомпактнее было):
            код
            constexpr double R=5000.0;
            
            struct position{
            	double lat;
            	double lon;
            };
            double distance(const position& p1, const position& p2)
            {
                const double sin_lat=sin((p2.lat-p1.lat)*M_PI/360.0);
                const double sin_lon=sin((p2.lon-p1.lon)*M_PI/360.0);
                const double a=sin_lat*sin_lat+cos(p1.lat*M_PI/180.0)*cos(p2.lat*M_PI/180.0)*sin_lon*sin_lon;
                return 2.0*atan2(sqrt(a), sqrt(1.0-a));
            }
            struct segment{
            	position begin;
            	position end;
            };
            unordered_map<string, list<segment>> segments;
            
            void on_new_segment(const segment& s, const string& id)
            {
            	static position p=s.begin;
            	if(distance(p, s.begin)<R||distance(p, s.end)<R) segments[id].push_back(s);
            }
            
            void on_error(const string& s) {wcout<<"Invalid DCF line: "<<s<<endl;}
            
            int main(int argc, char *argv[])
            {
            	auto_cpu_timer timer;
            	if(argc<2) return 1;
            	error_code ec;
            	for(directory_iterator i(argv[1], ec), e; i!=e; i++){
            		if(!is_regular_file(i->status())||i->path().extension()!=".dcf") continue;
            		wcout<<"Processing '"<<i->path().filename()<<"'..."<<endl;
            		mapped_file_source file(i->path());
            		double lat1, lon1, lat2, lon2;
            		string id;
            		parse(file.data(), file.data()+file.size(),
            			*(("SEG:">>double_[ref(lat1)=_1]>>':'>>double_[ref(lon1)=_1]>>':'>>double_[ref(lat2)=_1]>>':'>>double_[ref(lon2)=_1]>>':'>>
            			*("ClustID:">>(+(char_-eol-':'))[([&](auto& s){id={begin(s), end(s)};})]>>':'|+(char_-eol-':')>>':')>>eol)
            			[([&]{on_new_segment(segment{{lat1, lon1}, {lat2, lon2}}, id);})]|
            			("SEG:">>+(char_-eol))[([&](auto& s){on_error({begin(s), end(s)});})]>>eol|
            			+(char_-eol)>>eol));
            	}
            	wcout<<"Found "<<segments.size()<<" segments"<<endl;
            }
            


            1. akornilov Автор
              22.12.2017 01:53
              -1

              Если это действительно очевидные вещи, то тогда солидный кусок обсуждаемой статьи надо бы выкинуть… )))

              Размеры файлов и рантаймов вещи очевидные, а вот выводы которые я из них делаю это совсем другое, так что ничего выкидывать не будем :)

              Это уже не инженерные аргументы, а какие-то личные фобии.

              Недостаток оптимизации по размеру файла вполне себе аргумент, не знал что это фобия :)

              Вообще то Nim компилируется в C++

              Даже если он и компилируется в C++ давайте не будем забывать о жирном рантайме с кучей системных вызовов, без портирования их на новую платформу вы этот код не перенесете.

              Любители Кобола говорили также…

              Мало ли кто так говорил, от этого мысль не становиться хуже.

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

              О, вот за то что обнаружили эту багу респект вам и уважуха! :) вот что значит когда от чесания языком люди переходят к делу. Это действительно существенное замечание.
              Только попросил бы вас впредь быть поосторожнее с терминологией и избегать навешивания ярлыков типа «махинация». Это была большая небрежность с моей стороны, признаю, но в конце концов я именно для этого и выложил исходники чтобы все могли посмотреть и указать на ошибки.

              Исправил и сразу прогнал тест на Debian-е: Java идет прямо вровень со Scala. Погоняю побольше, обновлю результаты.

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

              Да и не надо утруждаться :) все равно это не годится. Смысл был в том чтобы на всех языках иметь одинаковую структуру приложения и классов. Чтобы затраты на создание и уничтожение классов тоже учитывались. Но за старания спасибо :)


              1. AlexPublic
                23.12.2017 02:01
                +1

                Недостаток оптимизации по размеру файла вполне себе аргумент, не знал что это фобия :)

                Если сравнивать статически скомпонованные нативные исполняемые файлы, то да, это нормально. К примеру если для решения одной и той же задачи статически скомпонованное приложение на C++ занимает 1 МБ, а на C++/Qt 7 MB, то вполне могут возникнуть вопросы об оптимизации Qt по данному вопросу. Однако если начинать сравнивать ещё и динамическую компоновку, причём беря в расчёт исключительно размер исполняемого файла (и наплевать, что рядом с ним валяется dll в 100 раз большего размера), то это уже становится чем-то из области психиатрии…

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

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

                Да и не надо утруждаться :) все равно это не годится. Смысл был в том чтобы на всех языках иметь одинаковую структуру приложения и классов. Чтобы затраты на создание и уничтожение классов тоже учитывались. Но за старания спасибо :)

                Хы, это же C++, язык основной концепций которого является нулевая цена абстракций. Можно навернуть там ещё хоть 150 классов типа Perfomer и от этого не только не поменяется производительность, но и даже не поменяется сам код (с точностью до бита) — компилятор просто выкидывает все эти лишние уровни абстракции в процессе оптимизации.

                Ну и да, в любом случае не стоит применять архитектуру приложения происходящую из языков с навязанным ООП (типа Java, C# и т.п.) к полноценным мультипарадигменным языкам. Тем более, что современный C++ всё больше уходит от ООП в сторону функциональных стилей и т.п.


    1. igormich88
      20.12.2017 15:10

      Я как то в качестве эксперимента ужал jre (на Windows) до примерно 13 мегабайт, удалив все неиспользуемые классы и dll. Но понятное дело что на таком урезаном jre кроме этой программы едва ли бы что запустилось. Собственно делал для того что бы можно было запустить мою игру на компе без Java и размер дистрибутива не растаращивался в 10 раз.


  1. third112
    20.12.2017 14:35

    Внимательно прочел статью и все комментарии. ИМХО одна из основных ошибок автора, что он в одной статье соединил оценку дизайна ЯП и оценку эффективности реализаций ЯП. Из того, что return@forEach выглядит непривычно, не следует, что это будет медленно работать. ИМХО стоило разбить статью на две части. (Первую сделать больше, чем сейчас). Тогда бы и ответы были бы конкретнее по каждой из частей. А так критика сосредоточилась в основном на эффективности. Тут ИМХО критикам возразить сложно: хорошо известно много методик оценок эффективности. Все они включают разнообразные тесты, а тут возникает впечатление, что получены результаты только по I/O разных реализаций разных языков. Думаю, что если автор возьмет задачу с полным перебором без I/O, то лидеры ЯП могут оказаться другими. Думаю, что и критики, если решат задачу автора и получат принципиально другие результаты по лидерам, окажутся гораздо убедительнее. Еще думаю, что минусы за статью и комментарии никого ни в чем убедить не могут.


  1. Hokum
    21.12.2017 09:48

    Подскажите, а как Вы запускали приложения? Я тут ради интереса произвел запуск приложения на Scala тремя способами:


    • из консоли sbt командой run
    • scala — jar файл для scala (scala test.jar)
    • Java — jar файл включающий библиотеки скалы, который может выполнятся только с java (java -jar test.jar)

    И получил три разных варианта. Самый быстрый — из консоли sbt. Первый примерно тоже самое, что и Qt, а со второго запуска, отработал почти вдовое быстрее. Два других работали в двое медленнее, чем на Qt. Причем это только показатель замеров программы, реально было еще дольше.


    1. akornilov Автор
      21.12.2017 12:30

      Запускал из консоли: java…
      Если sbt консоль работает под java и грузит классы в эту же JVM, возможно, и будет некоторый прирост производительности.