Pull to refresh
26
0
Вадим @vpisarev

Инженер ПО

Send message

Спасибо, поизучаю вопрос. На первый взгляд, не вижу проблем, по крайней мере в той области где я работаю (компьютерное зрение, машинное обучение), перекомпилировать компоненты программы при добавлении к ней новых фич или при изменении в структурах данных. Строгий контроль над типами лично по моему опыту очень сильно экономит время на приведение кода к рабочему состоянию. В дополнение к этому, он позволяет организовать перегрузку функций с буквально нулевым накладным расходом, т.к. нужная функция для соответствующего типа данных (или комбинации типов данных) выбирается в момент компиляции, а не в runtime. Если же вдруг необходимо сделать динамический dispatch, то можно использовать интерфейсы:

// generic interface for feature detectors
interface FeatureDetector {
    fun detect(image: uint8 [,], maxfeatures: int): (float*2) []   
}

// 2 implementations: SIFT and SURF
class SIFT : FeatureDetector {
   ... // SIFT parameters
}

fun SIFT.detect(image: uint8 [,], maxfeatures: int) {...}

class SURF : FeatureDetector {
   ... // SURF parameters
}

fun SURF.detect(image: uint8 [,], maxfeatures: int) {...}

...
var param_fdetector = "SIFT"
fun parse(args: string list) {
   | [] => {}
   | "-features" :: algoname :: rest =>
       param_fdetector = algoname.toupper(); parse(rest)
   ... // process other command-line parameters
   }
parse(Sys.arguments()) // read command-line parameters,
                       // set the preferred detector
...
fun apply_some_feature_detector(image: uint8 [,],
                                ~maxfeatures:int=1000)
{
    val fdetector =
       if param_fdetector == "SURF" {(SURF {...} :> FeatureDetector)}
       else {(SIFT {...} :> FeatureDetector)}
    fdetector.detect(image, maxfeatures)  
}

Теоретически - да, так и надо поступать. А практически вскоре появляются статьи вроде "времена жизни (lifetimes) — это одна из самых запутанных вещей в Rust, которая часто вызывает затруднение у новичков, даже несмотря на официальную документацию. ..."

Спасибо, посмотрел немного на Petalisp. Ну вроде как там есть определенные моменты где он действительно технологически опережает numpy. numpy это чисто библиотека операций над массивами, а тут предоставляются средства для написания собственных циклов, и возможно Лисп'овский компилятор из этого сможет сгенерировать нормальный код. Но синтаксис конечно на любителя Лиспа.

Про unique типы тоже сейчас посмотрел. Если честно, мне это направление не нравится. Я не очень понимаю как с этим работать не натыкаясь постоянно на необходимость делать глубокое копирование чтобы удовлетворить компилятор. Если уж хочется полной функциональной чистоты (хотя такое себе желание), более перспективное направление, с моей точки зрения, это современные неизменяемые структуры данных, такие как RRB вектора и HAMT хэш-таблицы. В Фикусе шаблонный тип 't vector как раз построен на базе RRB. Когда вектор создается с помощью comprehension, то, само собой, он пишется сразу, без создания копий при добавлении каждого элемента, поскольку мы уверены что в момент создания никто больше на него не ссылается:

val rng = RNG(0x12345u64)
// create big vector efficiently,
// no data is reallocated during the process
val bigvec = [< for i <- 0:99999999 {rng.uniform(0.f, 1.f)} >]
val parts = bigvec[:1000] + bigvec[10000:100000] +
            bigvec[1000000:] // reuse parts of the vector,
                             // almost no data is copied
val sz = size(parts)
for i <- 0:1000 {
    val idx = rng.uniform(0, sz)
    // random access is ~O(1) operation
    println(f"{i}. parts[{idx}]={parts[idx]}") 
}

К сожалению, пока аналогов RRB векторов для многомерных массивов не знаю. Вектор из векторов для каждой строки — это слишком неэффективно, да и не нужно, изображения не растут непрерывно вширь и в высоту. Может быть можно что-нибудь соорудить на базе quad-tree, oct-tree и т.д., например, разбить картинку на тайлы, ~128x128 пикселей, и эти тайлы поместить в quad-tree.

Не хотелось бы дополнительно фигурные скобки нагружать, они и так очень хорошо работают в Фикусе. Напротив, хотелось бы чтобы визуально директивы препроцессора хорошо искались и легко обрабатывались, в т.ч. внутри IDE. Вся реализация препроцессора - это отдельный шаг после лексического и до синтаксического анализа, ~400 строчек кода, большая часть которого - это вычисление выражений в @IF/@ELIF/@{}.

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

for y <- 0:h for x <- 0:w {
   B[y,x] = uint8((A.clip[y-1,x] + A.clip[y,x-1] +
         A[y,x] + A.clip[y,x+1] + A.clip[y+1,x]) >> 3)
}

кроме .clip еще можно .wrap (то есть из плоской картинки делаем тор) и .zero - дополнение нулями.

да, конечно. См. пример nbody.fx, там используются похожие структуры и определяются над ними операторы: https://github.com/vpisarev/ficus/blob/master/examples/nbody.fx

Фортран это очень мощный язык, и он конечно еще всех переживет (как и Лисп). Тем не менее, если взять свежую версию Фикуса, и запустить вот такой примерчик:

val a = [| for i <- 0:100 {i/100.*M_PI} |]
println(sin(a.**2))

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

for (int_ i_0 = 0; i_0 < 100; i_0++, dstptr_0++) {
   *dstptr_0 = sin(pow(i_0 / 100.0 * 3.141592653589793, 2));
}

То есть, компиляторы Фортрана круты, а библиотеки на Фортране бесценны, но и мы уже кое-что можем.

Именно так. В Фикусе реализованы иммутабельные вектора, добавленные под влиянием доклада Хуана Пуэнте: https://www.youtube.com/watch?v=dbFfpTp3EhA. Для представления потоковых одномерных данных типа звука, текста, финансовых данных и прочее они я думаю вполне бы подошли. Но для изображений, тензоров все-таки пока это не очень эффективно.

Обертки над BLAS и LAPACK в самых ближайших планах. Функций там много, но попробуем обернуть хотя бы часть для generic double-precision матриц (ленточные, верхне/нижнетреугольные пока можно отложить)

В препроцессоре Фикуса нет директивы include. Вместо этого есть настоящие модули. Тем самым, локально объявленные в модуле символы не влияют на остальные модули.

Во-вторых, символы препроцессора содержатся в отдельной таблице, и не конфликтуют с символами в коде.

// preprocess_test.fx
@IFNDEF UI
@DEFINE UI "CLI"
@ENDIF
val UI = @{UI} // публикуем значение символа препроцессора в настоящем коде,
               // сохраняем в неизменяемую переменную с таким же именем
println(f"selected UI: {UI}")

запускаем чтобы напечаталось "GUI"

`bin/ficus -run -D UI=GUI preprocess_test.fx`

Автовекторизация/раскрутка циклов в настоящий момент осуществляется только компилятором C/C++, который подхватывает код сгенерированный Фикусом. Наша первая задача - сгенерировать максимально дружественный для векторизации код, не отягощенный динамической типизацией, boxing/unboxing и т.д. Для простых циклов эта задача решается неплохо.

Compile-time выражения не имеют отдельного синтаксиса, но во время компиляции константные выражения подхватываются и заменяются их значениями. Например, при компиляции примера выше про LUT с -O3 (imax - imin) благополучно заменилось на 4080.

Примеров много и в тьюториале на 100 страниц и в подкаталоге examples. Собираюсь написать еще статью, может и не одну, с более подробным обзором фич языка с примерами. Помести я это все в эту статью, объем бы превысил все разумные пределы.

Генерации доков пока нет, это в планах, хотя и не краткосрочных

Теоретически вызвать Фикус из C/C++ должно быть несложно. Практически в данный момент это не так просто поскольку пока компилятор работает только в режиме генерации кода для приложений.

Вот пример, который воспроизводит ваш C++ код:

fun make_lut_16to8(imin: int, imax: int)
{
    assert(0 <= imin <= imax < 65536)
    [| for i <- 0:65536 {sat_uint8((i - imin)*255./(imax - imin))} |]
}

fun apply_lut(img: uint16 [,], lut: uint8 []) =
    [| for pix <- img {lut[pix]} |]

val L = make_lut_16to8(16, 4096)
val img8 = apply_lut([| for i <- 0:6 for j <- 0:6 {uint16((i+j)*500)}|], L)
println(img8)

Проблема в том что если собрать его с -O3 или -O1, то эти функции подставятся и исчезнут сами по себе. Пока нужно компилировать только с -O0, но тогда скорость будет ниже. Но тем не менее, для проверки можно это сделать. Допустим, если сохранить этот код в файле lutop.fx и собрать, то вы получите каталог ficus/__fxbuild__/lutop/ с кучей .с файлов, включая lutop.c, там будет функция FX_EXTERN_C int _fx_M5lutopFM9apply_lutA2b2A2wA1b(fx_arr_t* img_0, fx_arr_t* lut_0, fx_arr_t* fx_result, void* fx_fv), которую можно вызвать, если соответствующим образом подготовить входные массивы:

_fx_arr_t img = {0, 0, 0, 0, 2, (char*)src_ptr,
   {{height, src_stride}, {width, sizeof(uint16_t)}};
fx_arr_t lut = {0, 0, 0, 0, 1, (char*)lut_ptr, {{65536, 1}};
fx_arr_t result = {0};
int errcode=_fx_M5lutopFM9apply_lutA2b2A2wA1b(&img, &lut, &result, 0);
...

Подобная обертка может располагаться в .с/.cpp файле и она как раз может быть dllexport. После чего останется включить все сгенерированные .c файлы, вместе с runtime, в свой проект. Но конечно это пока не очень простое и не удовлетворительное решение.

Спасибо. Cтиль Julia немного отличается, мне кажется, скорее здесь параллели можно провести со Swift, Scala, Rust. Насчет Ocaml все справедливо - это основной источник влияния.

В качестве лайфхака - если взять редактор с поддержкой лигатур (например, Sublime или VSCode) и соответствующий шрифт, то будет еще красивее. В репозитории Фикуса можно найти один из таких шрифтов.

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

Ну и, справедливости ради, это не совсем DSL. Скорее полноценный язык (на котором можно написать компилятор например) с хорошей поддержкой массивов.

Это было бы полезно, и для пополнения unit-тестов нетривиальными примерами, и для продвижения языка. Один из примеров, ycomb.fx как раз был взят из rosettacode, потому что засомневался что оно вообще в Фикусе заработает. Вопрос только в ресурсах. Это был бы полезный студенческий проект, а может и не один. Надо будет это записать и подумать, какими силами это сделать.

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

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

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

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

Например бенчмарка btree значительно ускорилась после: 1) изменения представления рекурсивных вариантов, когда "пустые" листья стали представляться нулевыми указателями вместо выделения под них памяти, 2) подключения rpmalloc, который сейчас основной аллокатор в Фикусе и 3) распараллеливания бенчмарки по деревьям (@parallel for). Это общие полезные оптимизации. Дальнейшее ускорение скорее всего возможно при переходе с обычного подсчета ссылок на базе атомарных операций на т.н. biased reference counting, когда в большинстве случаев атомарных операций можно избежать. Опять же, это общая оптимизация которая будет полезна для многих программ.

Бенчмарки spectralnorm и mandelbrot должны хорошо ускориться после автоматического портирования некоторых циклов под GPU. Это интереснее и полезнее (хотя и гораздо сложнее) чем тьюнить код одной конкретной бенчмарки. Для mandelbrot также можно в одном месте чуть подкрутить компилятор. Возможно это позволит догнать суб-оптимальную/стандартную версию на C++.

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

Для n-body также можно в одном месте чуть подкрутить компилятор, но в-целом код неплохой. В реальности n-body это довольно игрушечная симуляция. Будет интереснее попробовать Фикус на гораздо более тяжелых симуляциях, и тогда опять же портирование на GPU должно сыграть.

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

С Джулией (Julia) интересно было бы сравниться, в следующей статье надо будет обязательно добавить. Это очень неплохой язык, где также реализована поддержка массивов на уровне Matlab, правда, насколько я понимаю, там выбрана динамическая типизация как вариант по-умолчанию, соответственно чисто теоретически эффективность кода будет скорее всего ниже чем в C++ и в Фикусе. На практике же и язык и компилятор и стандартная библиотека находятся в уже очень продвинутом состоянии, в компиляторе реализован вывод типов, есть возможность использовать SIMD-интринзики, есть возможность для конкретных случаев тонко настраивать код, например отключать проверку на диапазон при доступе к массиву, поэтому есть возможность при желании получить довольно быстрый код.

По поводу названия. Ну как-то так получилось, рассматривал названия, начинающиеся на F/Ф, чтобы подчеркнуть функциональность. Случайно узнал что Фикусовые - это целое семейство, включающее большое количество известных растений, от комнатных до совершенно эпических, используемых для совершенно разных целей. Меня такая универсальность и "масштабируемость" очень порадовала.

Прошу прощения, в конце ссылки магическим образом добавился лишний символ. Правильная ссылка https://github.com/vpisarev/ficus

не знание мешает, а использование в распространяемых программах. Для себя в заметках псевдокод на русском активно использую, но потом это всё равно превращается в документацию/комментарии на английском.
Проблема с русско-английской раскладкой пока удовлетворительно не решена, если говорить именно про набор программ/технических текстов с большим количеством спецсимволов. На каждый чих нужно переключать раскладку. Да, bigsur на маках может это делать по клавише fn, на Windows можно поставить программку и переключать по правому альту, но всё равно неудобно.

Что касается языков, то:

1. большинство нормальных американских программистов («хакеров» в терминологии Пола Грэма) терпеть не может многословные языки типа Ады (создана не хакером для хакров, а по госзаказу) или Паскаля (создан швейцарцем Никлаусом Виртом, для которого английский не является родным). Они обожают Lisp, C/C++ и подобные языки. Я как-то спросил американца, как они воспринимают программы на Паскале, на что получил ответ: «well, they look weird». Соответственно, нет никаких оснований что русско-язычные профессионалы будут с энтузиазмом пользоваться языком с ключевыми словами «если», «то», «иначе», «фунция», «начать», «кончить» и т.д.

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

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

4. писать комментарии на русском и называть переменные/функции по-русски можно уже сейчас в большинстве языков программирования, например:

C++:
#include <iostream>
#include <string>
using namespace std;

int main(int, char** argv)
{
    string привет = "привет", мир = "мир";
    auto& вывод = cout;
    вывод << привет + ", " + мир + "!\n";
    return 0;
}


Python:
привет = "привет"
мир = "мир"
печать = print

печать ("%s, %s!\n" % (привет, мир))


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

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

Information

Rating
Does not participate
Location
Нижний Новгород, Нижегородская обл., Россия
Registered
Activity