macroevolution: (anomalocaris)
macroevolution ([personal profile] macroevolution) wrote2015-01-11 02:41 pm

Посоветуйте язык программирования

Я всю жизнь программировал только на бейсике, на разных его версиях.  Так получилось. Когда писал кандидатскую, набралось очень много таблиц с данными (это были морфологические признаки морских ежей), и я задолбался обсчитывать их на калькуляторе. Поэтому быстренько освоил бейсик (тогда персональные компьютеры IBM только начали появляться, и к ним прилагался язык GW-Basic). Освоил - и сразу почувствовал себя человеком. С тех пор не переучивался, сейчас пишу все свои программки на VBA в MS Access. То есть в программировании я дилетант, но опытный. Программированием пользуюсь сейчас для имитационного моделирования эволюционных процессов в популяциях. Подумываю об одной новой модели, но понимаю, что на VBA она будет работать невыносимо медленно. Насколько я понимаю, программа, написанная почти на любом другом языке, компилируемом, будет работать в разы быстрее. Вопрос такой: какой из этих языков мне будет быстрее и проще всего освоить? Времени, сил и желания преодолевать трудности и вникать в программистские проблемы - не имеется. Мне бы этот язык просто скачать (можно купить, если не слишком дорого), освоить за пару-тройку дней - и вперед. Т.е. главное, чтобы он был максимально простым в освоении для того, кто знает бейсик, без всяких интеллектуальных "понтов", но работал хотя бы раз в 10 быстрее.

[identity profile] agalakhov.livejournal.com 2015-01-11 04:56 pm (UTC)(link)
Кстати, для вычислительных алгоритмов над большими массивами языки с чистым GC (вроде java) имеют очень спорную пригодность именно из-за склонности GC освобождать память слишком поздно. Когда вычисления и так требуют гигабайты (а для решения уравнений, сводящихся к диагонализации матриц, это не редкость), дополнительный расход от опоздания GC часто становится фатальным и приводит к тому, что программа вообще не работает на данном железе. Именно поэтому программы для научных расчетов крайне редко пишутся на Java, чаще используются Fortran и Python, чуть реже C.

[identity profile] pssshik.livejournal.com 2015-01-11 05:18 pm (UTC)(link)
Ну память нынче очень дешевая - это не проблема (смотрю на жаба веб сервер с 45 гб выделенной под него памяти). Жаба просто не приспособлена для научных расчетов - ну не удобно создавать самому всю инфраструктуру, ввод и вывод - в жабе нет по умолчанию даже комплексных векторов и матриц, библиотеки, которые с ними работают - какие-то дикие. Еще минус - сверхжесткая типизация: описывать каждую финтифлюшку как отдельный класс, делать дерево наследования, чтобы использовать один метод для разных классов - та еще забава.

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

[identity profile] agalakhov.livejournal.com 2015-01-11 05:29 pm (UTC)(link)
Питон не коряв, он просто очень непривычен. С практической точки зрения он как раз очень удачный - очень мало языковых сущностей, практически все является просто синтаксическим сахаром поверх пары простейших внутренних операций, и за счет этого можно очень быстро писать очень сложные вещи. (Мой личный рекорд - полноценный работающий транслятор PHP-подобного языка, написанный с нуля за 40 минут).

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

[identity profile] agalakhov.livejournal.com 2015-01-11 05:36 pm (UTC)(link)
А память я бы не стал считать "не проблемой". Научные расчеты всегда упирались как раз в память. Большинство практических задач по памяти имеют O(n^2) или O(n^3), поэтому удвоение и даже учетверение памяти - это слону дробина. Я помню, на 256 мегабайтах мы могли едва-едва делать расчет для 20 атомов, а реально хотелось бы для нескольких тысяч. Для таких вещей и терабайта не хватить может. Поэтому приходится очень аккуратно оптимизировать расход памяти: экономия пары байтов в структуре, которая создается миллиард раз - вот уже и пару гигабайт сэкономили.

В обработке больших данных, которой я тоже позаниматься успел, та же петрушка. Когда база весит около 100 гигов и когда стремишься засосать в ОЗУ максимум, сколько туда вообще влезет, чтобы не прыгать головкой по диску, считать память начинаешь тоже уже очень аккуратно. 16 гигов на один массив, 16 гигов на другой, и следишь, чтобы все это объем физической памяти не превысило, а то улетишь в своп.

[identity profile] pssshik.livejournal.com 2015-01-11 05:50 pm (UTC)(link)
ну база на 100 гигов - это фигня, если честно, т.к. можно в сервер и 128гигов набить. если нужно рандомное чтение-запись - ну ссд диски уже вовсю используются. расчитывать терабайт на одном серваке - ну это неэффективно уже по процессорным мощностям, так что терабайт вычислений будет раскидан по 5-10 машинам, что реально уже сейчас, а завтра будет просто общим местом. Проблема обьект-спамминга (когда на простую операцию создается класс обертка, несущий в себе класс, который является посредником для общения с другим классом, а потом передается другому свежесозданному классу,... ) в яве стоит остро, но это уже уровень программиста.

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

[identity profile] agalakhov.livejournal.com 2015-01-11 06:52 pm (UTC)(link)
> ну база на 100 гигов - это фигня, если честно, т.к. можно в сервер и 128гигов набить.

Если база ОБРАБАТЫВАЕТСЯ, то для промежуточных результатов и словарей требуется ОЗУ, в несколько раз превышающее объем базы. Практически при 128 гигабайтах памяти предел объема базы, при котором такая обработка еще эффективна, составляет 30-40 гигабайт. Если надо больше, то обработку приходится делать уже оффлайн, на дисковых файлах и не в реальном времени.

> ООП эффективно для задач, которые легко представляются в виде диаграммы обьекты-акторы-действия

Но в таких задачах уже вовсе необязательно делать ООП на уровне языка, достаточно на уровне библиотек. ООП создавалось как раз для решения таких задач (Smalltalk), но потом были придуманы методы поинтереснее и с меньшим количеством подобных эффектов.

[identity profile] akheront.livejournal.com 2015-01-15 11:36 am (UTC)(link)
Память дешёвая была летом. Сейчас кусается.

[identity profile] pssshik.livejournal.com 2015-01-15 02:22 pm (UTC)(link)
Память кусалась пять (10, 15) лет назад - сейчас дешевая и будет только дешеветь :)

[identity profile] akheront.livejournal.com 2015-01-15 07:06 pm (UTC)(link)
Да что вы говорите. Валюта дорожает, а память дешеветь станет? Она же импортная вся.

[identity profile] chebudzee.livejournal.com 2015-01-29 01:27 pm (UTC)(link)
Что за бред? В питоне тоже есть GC и оно работает гораздо медленне чем java.

[identity profile] agalakhov.livejournal.com 2015-01-29 02:13 pm (UTC)(link)
Вы очень грубо ошиблись. В питоне менеджмент памяти основан не на GC, а на прямом счете ссылок. GC там вспомогательный и исполняется крайне редко (в основном - на неправильно написанном коде, чтобы разрывать кольцевые ссылки).

К тормозам Питона GC отношения вообще не имеет. Все дело в том, что РЕФЕРЕНСНАЯ (и только референсная!!!) реализация Питона - так называемый CPython - это чистый интерпретатор. В нем нет JIT совсем-совсем. Поэтому он, как все чистые интерпретаторы, очень медленный: ему каждую переменную приходится искать в памяти ПО ИМЕНИ, в худшем случае за O(log(N)). К другим реализациям питона это не относится. В частности, PyPy - это JIT, и на тестах арифметики он по производительности ПРЕВОСХОДИТ Java, иногда процентов на двадцать. Во многом именно за счет того, что счет ссылок, в отличие от GC, поддается частичному развертыванию на compile-time (см., например, Ахо, Сети, Ульман, "Компиляторы: принципы, технологии, инструменты", главы 7 и 12 по 2-му изданию).

Что касается NumPy для вычислений, то NumPy даже в CPython работает с максимальной возможной скоростью, ибо написан на Си, местами с оптимизацией на ассемблере. Он быстрее даже фортрана.

[identity profile] pphantom.livejournal.com 2015-01-29 04:32 pm (UTC)(link)
А можно увидеть пример ситуации, когда NumPy обгоняет Фортран?

[identity profile] agalakhov.livejournal.com 2015-01-29 08:43 pm (UTC)(link)
Запросто. numpy.linalg.eig для действительно большой матрицы. Тогда питонный код будет заниматься только чтением исходных данных и выводом результата, т.е. на время работы почти не повлияет, и получится просто соревнование между компилятором Фортрана и компилятором Си. Лет десять назад победил бы фортран, но на современных компиляторах с оптимизацией под суперскалярный процессор фортран и си либо вообще равны (и генерируют почти идентичный машинный код), либо си чуть быстрее (в силу большей свободы оптимизации, которую стандарт си дает по сравнению со стандартом фортрана).

[identity profile] pphantom.livejournal.com 2015-01-29 09:32 pm (UTC)(link)
Это же просто вызов из LAPACK, написанной на Фортране. За счет чего произойдет обгон?

[identity profile] agalakhov.livejournal.com 2015-01-29 10:36 pm (UTC)(link)
Это не вызов LAPACK. Это вызов другой библиотеки, делающей то же самое. Она, скорее всего, оптимизирована немножко лучше, чем референсный LAPACK, за счет этого и выигрыш. Впрочем, фортрану тоже никто не запрещает пользоваться той же библиотекой, но это программист должен сделать вручную :)

Вообще в фортране обычно получается хорошая скорость на самих вычислениях, но плохая на вызовах функций. Соглашение вызовов фортрана более архаичное, чем си, оно фактически требует честную передачу параметров через стек и запрещает инлайн. Фортран стабильно выигрывал у Си, пока Си не умел инлайнить и подавлять лишние переменные. Соответствующие алгоритмы для Си появились где-то в середине 2000-х. Ключевой из этой серии - алгоритм LSRA - в конечном итоге породил бум на JIT-компиляторы и привел к созданию LLVM. Это алгоритм поиска оптимального размещения переменных в регистрах за O(n), а не за O(n*log(n)), вокруг него все теперь и строится. Компиляторы фортрана его тоже теперь используют, но в фортране от него проку мало, поскольку многие переменные по стандарту обязаны лежать в памяти.
Edited 2015-01-29 22:38 (UTC)

[identity profile] pphantom.livejournal.com 2015-01-30 12:29 pm (UTC)(link)
В каком смысле "вручную"?

[identity profile] agalakhov.livejournal.com 2015-01-30 01:07 pm (UTC)(link)
В том, что вызов библиотеки в фортране содержится явно. Программист, а не компилятор, выбирает, какую версию библиотеки он будет использовать. Я не могу в фортране просто написать "хочу BLAS", я должен указать, какой именно BLAS и откуда его брать. Мне лично несколько раз приходилось вносить такие изменения именно в фортранный код, заменяя какой-то левый BLAS на мой ATLAS. Ускоряет, кстати, процентов на десять по моим измерениям.

В этом принципиальное отличие от "встроенных в язык" библиотечных возможностей и от "установленных в систему" библиотек. Когда я пишу import numpy в питоне или std::sort в C++, я могу рассчитывать на то, что использую наилучшую из имеющихся на данном компьютере реализаций. Если я обновлю систему, и новая версия диагонализации матриц в ней начнет использовать видеокарты через CUDA, мне для этого ничего не придется делать со своим кодом. Он просто начнет магически работать быстрее.

[identity profile] pphantom.livejournal.com 2015-01-30 05:22 pm (UTC)(link)
Ну, это не совсем так. Сборка с ключом -lblas приведет ровно к тому же эффекту - будет использована та реализация, которая установлена в системе, с точки зрения основной программы ничего не изменится.

(no subject)

[identity profile] agalakhov.livejournal.com - 2015-01-30 18:09 (UTC) - Expand

[identity profile] agalakhov.livejournal.com 2015-01-29 08:56 pm (UTC)(link)
На самом деле примерно с тем же успехом можно сравнивать разные реализации BLAS - чисто фортранную и ATLAS, например. Когда быстродействие сводится к библиотекам, написанным на компилируемых в машинный код языках, побеждает та, которая оптимизирована лучше. От языка программирования это зависит постольку, поскольку стандарт языка ограничивает допустимую оптимизацию (например, стандарт фортрана запрещает повышать точность вычислений сверх указанной, даже если это приводит к повышению скорости). Оптимизаторы в современных компиляторах общие для всех языков, после стадии промежуточного кода компилятор "забывает", с какого языка компилирует.

Кстати, современные JIT-движки генерируют код почти такого же качества, как простые компиляторы. Поэтому PyPy такой быстрый. Он работает на том же LLVM, что и clang, и все его тормоза связаны исключительно с рантаймом питона (динамические аллокаторы, приведения типов и т.д.). На чисто числодробильном коде отставание от си и фортрана становится несущественным, хоть и все еще заметным.

[identity profile] pphantom.livejournal.com 2015-01-29 09:38 pm (UTC)(link)
С первым абзацем можно согласиться (почти) полностью, а вот со вторым... Как-то в обычной жизни картина выглядит так: если писать код без внимательного отслеживания оптимизации, то Фортран раза в полтора-два обгоняет Си, все варианты Питона теряются где-то вдали. Действительно аккуратное написание кода на Си позволяет догнать (но не перегнать) Фортран (правда, ценой существенно больших усилий программиста), а все варианты Питона остаются примерно там же, где и ранее.

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

[identity profile] agalakhov.livejournal.com 2015-01-29 10:20 pm (UTC)(link)
Если программист пишет код на СИ без внимательного отслеживания оптимизации, то его надо увольнять. В 21-м веке для некритичного по скорости кода существуют более безопасные и удобные языки. Код на Си теперь по определению критичен по скорости.

Более того, начиная со стандарта C99 (именно стандарта!) это больше неверно. Стандарты C99 и особенно C11 разрешают компилятору очень сильно отклоняться от написанного программистом, если такое отклонение не запрещено явно. (strict aliasing rules, например). Вплоть до перестановки местами полей в структурах и засовывания таких полей в регистры, если на них не берется указатель. (Демонстрация: int func() { int x[1000000]; x[5] = 7; return x[5]; }, скомпилировать gcc -O2 -S и удивиться). В фортране подобные вольности запрещены стандартом. Поэтому написанный БЕЗ ОШИБОК (именно без ошибок, без глупых попыток "оптимизации" без понимания сути) код на Си, скорее всего, соптимизируется очень хорошо. Оптимизатору тут не надо помогать, ему достаточно не мешать. Существенно тут то, что оптимизатор Си может легко задействовать суперскалярные инструкции современных процессоров, а оптимизатор Фортрана почти никогда не может (хотя бы потому, что они не всегда точно соответствуют IEEE-арифметике).

Библиотека numpy - НЕ ФОРТРАНОВСКАЯ. Она почти полностью на Си написана.

[identity profile] pphantom.livejournal.com 2015-01-30 12:28 pm (UTC)(link)
Это если программист. На практике вычислительным программированием сейчас редко занимаются те, у кого программирование - основная профессия, а не "сопутствующий навык".

Да, такой источник оптимизации существует. Но срабатывает это все же достаточно редко: тот же приведенный пример явно не из реальной жизни. И, кстати, где в стандарте Фортрана находится запрет на "подобные вольности"? Например, в 2003-м.

[identity profile] agalakhov.livejournal.com 2015-01-30 02:00 pm (UTC)(link)
Приведенный пример почти из реальной жизни, только он характерен не для C, а для C++. Там за счет шаблонов очень часто порождается лишний код - временные объекты и ссылки, бессмысленные копирования и т.д. Раньше из-за этого C++ был медленным, и это породило миф, что C++ медленнее C. Это давно уже не так - современные компиляторы вырезают такой код так же эффективно, как и порождают, особенно если используется STL-библиотека с поддержкой C++11 (конкретно - move constructor). У меня есть примеры кода, которые из 100-200 строк сворачиваются в 1-2 машинных команды. (Зачем так писать? Затем, что это нужно для гибкости - изменение всего одной строчки приводит к генерации совсем другого кода. Это способ решения задач в условиях почти полного отсутствия техзадания - "сделаем лего, а потом из лего быстренько соберем решение").

Современный Си оптимизирует вычислительное программирование не хуже, чем Фортран. Раньше была проблема, что написание указателя автоматически означало, что переменная честно ляжет в память и будет честно взят указатель. Сейчас не так, сейчас компилятор лишние указатели подавляет. И даже лишние if умеет на арифметику заменять. Поэтому код на си и код на фортране скорее всего скомпилируются просто в идентичный машинный код, даже если их пишет человек с "сопутствующим навыком". Главное, чтобы этот человек не пытался "умничать" и использовать всякие "оптимизации", о которых прочитал в учебнике по Си 1989 года издания. Вот этим реально можно оптимизатор раком поставить. Например, написать malloc() вместо простого int x[100], побоявшись, что 100 элементов - это много. (Люди добрые, да современный стек массив из миллиона элементов скушает и не подавится!) Вообще, чем проще и примитивнее написана программа, тем легче оптимизатору ее понимать.

Гадости из стандарта фортрана 2003 - это, например, параграф 6.2.2.2 "Array element order", который зачем-то регламентирует расположение элементов массива в памяти. Это имеет смысл только при грязном касте через передачу в процедуру не того типа или через EQUIVALENCE. Второе имело смысл только во времена БЭСМ, первое использовалось для самодельных аллокаторов в Fortran-77 и утратило смысл в Fortran-90. Но фактически этот параграф запрещает компилятору раскладывать короткие массивы по регистрам. А значит, в программе, в которой много трехмерных векторов и скалярных произведений, компилятор, не нарушая стандарта, не сможет распихать эти массивы по регистрам и использовать инструкции SSE. Вместо того, чтобы делать скалярное произведение одной инструкцией, он сделает честный цикл из перемножений и сложений. Чтобы уж совсем забить гвоздь в крышку гроба SSE, есть еще вот такой параграф: 16.4.3.1 (7) A nonpointer array occupies a sequence of contiguous storage sequences, one for each array element, in array element order. Ну а весь параграф 16.4.3.3 "Association of scalar data objects" - это вообще гадость, не имеющая аналогов ни в каких современных языках программирования. Как раз та штука, с которой современная теория компиляторов вообще справляться не умеет.

[identity profile] pphantom.livejournal.com 2015-01-30 05:24 pm (UTC)(link)
Спасибо, убедительно.

(no subject)

[identity profile] agalakhov.livejournal.com - 2015-01-30 17:52 (UTC) - Expand

[identity profile] agalakhov.livejournal.com 2015-01-30 02:21 pm (UTC)(link)
Наверное, тут надо пояснить, почему все перечисленное так плохо для оптимизации. По теории, перед оптимизацией программу надо представить в так называемом виде "static single assignment". То есть считать, что каждая переменная присваивается ровно один раз, а каждое новое присваивание порождает новую переменную (старая после этого никуда не девается, просто ее больше не используют). Полученная программа будет иметь вид набора инструкций z=f(x,y), из которых потом строится граф потоков данных. Потом этот граф упрощают эквивалентными преобразованиями.

Если регламентировано расположение переменных в памяти, то присваивание представляется как две операции. Примерно так:
var tmp = x;
save(&y, tmp);
Операции "save" можно переставлять по коду с места на место, но эквивалентных преобразований, уничтожающих такую инструкцию, не существует. (Компилятор может, однако, породить пару save-load из других инструкций, если ему не хватает регистров.) Поэтому при разработке современных языков стараются свести появление таких штук к минимуму. В Си есть ровно два оператора, порождающих явный save: это volatile и это вызов внешней библиотечной функции. Вызовы inline и static не порождают save. В Фортране save по стандарту порождается каждым оператором создания массива.

Этим, кстати, очень просто объясняется, как же gcc "магически" уничтожил массив из 1000000 элементов. На этапе компиляции он рассматривался не как массив, а как 1000000 отдельных переменных, для которых не было регламентировано расположение в памяти. Из них 999999 - переменные, которые никогда не читались. Во всем коде была максимум одна операция save, порожденная return из функции (если нестатическую функцию не вызывают, она рассматривается как библиотечная для внешнего использования), и эквивалентные преобразования свернули весь граф потоков данных в две вершины: создать 7 и записать 7 в ответ. Эти две вершины, в свою очередь, порождают одну машинную инструкцию - записать 7 в регистр.