Я пишу этот комментарий ещё не прочитав вторую часть, но уже сейчас я со многим не согласен. Память для кадра локальных переменных функции можно выделять на стеке или в куче, оба этих региона ограничены, не только стек. На нём сильно проще выделить память (константное время) и проще вернуть.
Но есть ещё один момент. В функциональных языках поддерживаются значения-функции, которые запомнили контекст, в котором были определены, включая локальные переменные этого контекста. Такое хранилище контекста функции называется "замыкание"/"closure", и, поскольку для него порядок создания не соответствует обратному порядку удаления, оно создаётся на куче. А раз уж там живут локальные переменные, то имеет смысл его использовать в качестве кадра локальных переменных функции.
Как-то маловато. Этот прототип нельзя назвать HTTP-сервером, потому что тут нет парсинга HTTP, оно на любой однобайтный коннект сработает. А парсинг HTTP - это же самое интересное, потому что напрямую влияет на работу с NIO. Например, в handleRequest() зачем-то каждый раз аллоцируется ByteBuffer, хотя содержимое HTTP-запроса может приходить несколькими кусками. Правильный способ - привязывать ByteBuffer к socketChannel-у, и умело работать с ByteBuffer-ом через compact()/flip(). А по этой статье вообще не ясно, зачем flip() вызвали.
Ну и так, пара косяков. Первое: вы закрываете socketChannel, но оставляете его SelectionKey в Selector-е, это неправильно, нужно на ключе делать cancel(). Второе: вы делаете write() на неблокирующем канале, и даже не проверяете записалось оно или нет.
Спектрум был неплохой машиной для игр и обучения программированию, но разрабатывать на нём и сразу на месте тестироваться было больно: ассемблер и исходники программы занимали память, которой и так было немного, плюс программа могла зависнуть, погубив среду разработки. Из-за этого для разработки использовали разные непростые и недешёвые варианты. Вот тут утверждают, что у Ultimate было что-то типа многопользовательской Unix-машины на 68000 с кросс-компиляцией. Эх, нигде нет деталей.
Согласен, насчёт константости - скорее всего враньё. А насчёт скорости: если посмотреть документацию на vers-vecs , то там написано
This crate uses compiler intrinsics for bit manipulation. The intrinsics are supported by all modern x86_64 CPUs, but not by other architectures. There are fallback implementations if the intrinsics are not available, but they are significantly slower. Using this library on x86 CPUs without enabling BMI2 and popcnt target features is not recommended.
The intrinsics in question are popcnt (supported since SSE4.2 resp. SSE4a on AMD, 2007-2008), pdep (supported with BMI2 since Intel Haswell resp. AMD Excavator, in hardware since AMD Zen 3, 2011-2013), and tzcnt (supported with BMI1 since Intel Haswell resp. AMD Jaguar, ca. 2013).
Всё правильно Серёга написал, это только особенность языка, что вызов super() должен быть первым оператором в конструкторе, на уровне байт-кода это не проверяется.
Я проверил это так. Написал пару классов:
public class Parent {
private final String name;
public Parent(String name) { this.name = name;}
public String getParentName() { return name;}
}
public class Child extends Parent {
private String myName;
public Child(String childName, String parentName) {super(parentName);this.myName=childName;}
public String getFullName() { return "I am " + myName + " , a child of a " + getParentName();}
public static void main(String[] args) {
Child child = new Child("ChildClass","ParentClass");
System.out.println(child.getFullName());
}
}
Скомпилировал, и через javap дизассемблировал. Конструктор класса Child выглядит вот так:
До меня не сразу дошло, в чём суть проблемы Identity. Если нужно проверить, что две ссылки указывают на один и тот же объект через равенство, то им не обязательно быть именно ссылками на память, можно формировать их на основе полей объекта, и это сработает. Но проблема Identity состоит в том, что ссылки на два разных объекта с одними и теми же полями обязаны быть не равны.
Эта статья вызвала мой интерес не тем, что она про нейросети, а видом сжатого С-кода. Дело в том, что очень похожий стиль принят среди разработчиков на языках k и q, и интерпретаторы этих языков часто пишут в таком сжатом стиле, который мне даётся с трудом. Я подумал, что ну вот, может тут расскажут, как писать такое, поделятся секретами. А нифига, я сходил на github, и там исходники лежат в гораздо более понятном виде, и питоновский скрипт, который выполняет сжатие. Но при ближайшем рассмотрении оказалось, что питоновский скрипт делает довольно тупые действия, и автору в основном исходнике приходится самому прибегать к препроцессорной магии.
Когда я дошёл до раздела, где объясняется препроцессорная магия для матрично-скалярных и матрично-матричных операций, я сначала нифига не понимал, до тех пор, пока исходники не почитал. Дело в том, что в тексте вроде как define-ится BINARY с двумя аргументами (function и operation) в нижнем регистре, а потом в теле этого BINARY эти же аргументы стоят в верхнем регистре (FUNCTION и OPERATION), это очень сбивает с толку. Это не проблема перевода, в оригинальном тексте тоже так, в исходниках всё норм (fn и opr везде). Ну и раз уж зашла речь про BINARY, то я бы в начале проверил, что размеры матриц совпадают, fail fast и всё такое. Забавно, что он вводит поэлементное умножение и деление матриц, я такого не видел в математике, но у него находит применение в LayerNorm.
В описании математического матричного умножения у автора тоже косяк, в первом, неоптимизированном варианте остались k2 и j2.
Эта статья слишком коротка. Я с первого раза ничего не понял, рассуждения показались мне слишком абстрактными. Потом я взглянул на вашу библиотеку, и понял, что мне не показалось.
newtype W_II_II_I w i ii = W_II_II_I (w ii ii i)
Я не настоящий хаскеллист, что такое функтор я понял, что такое монада - тоже понял, стрелку уже не осилил, а вот этих ваших абстракций спасибо не надо.
Что касается вашей исходной посылки про сложности предметных областей, то программирование не создано, чтобы бороться с этим.
Самый простой способ написать эмулятор и виртуальную машину - это написать интерпретатор, с центральным бесконечным циклом, внутри которого будет происходить выборка команд с последующим switch-ем по опкодам. В случае эмулятора нет смысла делать что-то другое (типа threaded code), потому что после каждой команды нужно проделать некоторые общие вещи: посчитать число тактов, проверить, не возникло ли прерывание, проэмулировать другие компоненты, помимо CPU. Кроме того, архитектура реальных машин такова, что код хранится в памяти, то есть может быть изменён в процессе выполнения, и стек тоже находится в памяти. Это значит, что JIT/AOT применять не получится. А виртуальная машина - она "виртуальная", потому что у неё, как правило, отдельно память данных, отдельно хранилище кода, отдельно локальные переменные, отдельно стек возвратов, отдельно стек операций. То есть высокоуровневые вещи, ограничивающие всякие трюки в коде, но позволяющие JIT/AOT и прочие оптимизации.
То есть для эмуляторов отказываться от цикла со switch-ем - это, с одной стороны, усложнение кода, а с другой стороны - вообще не нужно, всё равно скорость и задержки надо эмулировать.
Вообще не в тему комментарий. Эмулятор конкретного железа не является байткод-машиной, потому что должен повторять потактово время выполнения инструкций эмулируемого железа, нет никакой необходимости добиваться некоей "максимальной" производительности.
Отличная статья! Поймал себя на том, что смотрю на нули и единицы в закодированном представлении bitmap-а, и вижу мышку.
Хочу спросить про ассемблерную процедуру: вы в ней испортили HL и A, их не надо было сохранять и восстанавливать?
И про сам язык хотел спросить. Вы положили в массив и размеры битмапа, и сам битмап. А нет в языке поддержки struct, чтобы длину и ширину класть в именованные поля?
Я не понял, зачем это может быть нужно. Я не спец в системе модулей Scheme, и не понимаю, что такое "динамическая подгрузка" модулей, но разве модуль - это не набор define-ов, которые все одновременно попадают в область видимости? Если функция инициализации попала в область видимости, то и всё остальное содержимое модуля тоже попало.
Я догадываюсь, что это может быть полезно во всяких макросах.
По мере чтения статьи у меня возникали разные вопросы, но потом я сам находил на них ответы. Чтобы это не пропало, я это сюда в виде комментария сброшу.
Если я правильно понял, то идея демо такая: буфер не очищается, на каждом шаге в него добавляются белые точки, и потом проходит блюр. Наверное, при многократных исполнениях такого цикла изображение должно стремиться к белому. Но операция деления на 4 проходит с отбрасыванием дробной части, что, в свою очередь, даёт смещение в сторону чёрного. То есть подобрав количество добавляемых белых точек можно получить относительно стабильный баланс серого.
Я так понимаю, что способ определить начало буфера через добавление константы к сегментному регистру работает, потому что тупо есть органичение, что com-файлы не могут быть больше 64кб, поэтому нефиг заморачиваться.
В том цикле, который палитру создаёт, вам точно надо mov ax, 1010hделать каждый раз внутри цикла? Может вынести в начало?
С блюром у вас занятно: для самого верхнего ряда вы обращаетесь и к предыдущим 320 байтам, которые вообще не в буфере. Кроме того, поскольку вы счётчик цикла инициализируете в 0000h, то цикл обработает 10000h байт, хотя достаточно обработать fa00h байт. Но, поскольку вы стремитесь к уменьшению размера программы, а не к скорости, то это понятно.
В цикле блюра можно было бы обойтись одним регистром, тем же SI. Я думаю, что inc si выставит флаг нуля, и вместо loop сделать переход через jnz. Если SI для этого не подойдёт, то его в цикле можно заменить на BX, он тоже умеет в адресацию со смещением.
Не могли бы вы подтвердить ваш комментарий конкретикой: подробнее рассказать, какие именно легаси и костыли в IDEA ? Особенно интересно послушать про "кучу".
Пользуюсь IDEA уже дохреналлион лет, сижу на Community и буду продолжать. JetBrains - молодцы, делают отличный продукт. Spring boot знаю, но использую по минимуму, стараюсь избегать участия в разработке CRUD-сервисов. Плагин amplicode не использовал, чуть знаком: но на прошлой неделе в нашей организации был воркшоп, на котором этот плагин презентовал сотрудник фирмы-разработчика, очень толковый парень по имени Илья. Очень надеюсь, что мне не придётся писать сервисы, в котором этот плагин бы пригодился, все эти JPA, мапперы, контроллеры - ну тоска же зелёная! К инициативе по перепаковке чужого кода отношусь нейтрально, является ли это IDE, JDK или линукс. К заманухам про "бесплатность" и "открытость" отношусь скептически, потому что IDEA CE также бесплатна и открыта. Пользоваться перепакованной IDE не буду. Сообщества там нет и не будет. К автору статьи, который отмечается под каждым комментарием, отношусь отрицательно.
Если сотрудник в процессе работы чувствует, что недостаточно профессионален для выполнения задачи или вообще для должности/позиции, пришёл к руководителю признать этот факт и попросить помощи - то это высокая степень профессионализма. Статья же напоминает известные шутки "Ну вы же профессионал/коммунист".
Дрон ощущается хорошо, с ним хорошо придумано. Пейзажи выглядят норм, реалистично, а персонажи выглядят и анимированы очень нереалистично, это очень заметно на фоне пейзажей.
Я позавчера поиграл в демо, мне понравилось, автор молодец. Головоломки несложные, атмосфера серой тоскливой зимы передана хорошо. Многие игры делают вид, что у них сложный мир, оригинальный сюжет и им есть что сказать, и эта игра не исключение, но я уже привык. Свободы исследования нет, всё линейно, стенки невидимые кругом и двери неоткрывающиеся.
Я пишу этот комментарий ещё не прочитав вторую часть, но уже сейчас я со многим не согласен. Память для кадра локальных переменных функции можно выделять на стеке или в куче, оба этих региона ограничены, не только стек. На нём сильно проще выделить память (константное время) и проще вернуть.
Но есть ещё один момент. В функциональных языках поддерживаются значения-функции, которые запомнили контекст, в котором были определены, включая локальные переменные этого контекста. Такое хранилище контекста функции называется "замыкание"/"closure", и, поскольку для него порядок создания не соответствует обратному порядку удаления, оно создаётся на куче. А раз уж там живут локальные переменные, то имеет смысл его использовать в качестве кадра локальных переменных функции.
Блин, где вы отрыли фотографию МакКарти без бороды? На каноничных фотографиях он с шикарной бородищей и шевелюрой.
Как-то маловато. Этот прототип нельзя назвать HTTP-сервером, потому что тут нет парсинга HTTP, оно на любой однобайтный коннект сработает. А парсинг HTTP - это же самое интересное, потому что напрямую влияет на работу с NIO. Например, в handleRequest() зачем-то каждый раз аллоцируется ByteBuffer, хотя содержимое HTTP-запроса может приходить несколькими кусками. Правильный способ - привязывать ByteBuffer к socketChannel-у, и умело работать с ByteBuffer-ом через compact()/flip(). А по этой статье вообще не ясно, зачем flip() вызвали.
Ну и так, пара косяков. Первое: вы закрываете socketChannel, но оставляете его SelectionKey в Selector-е, это неправильно, нужно на ключе делать cancel(). Второе: вы делаете write() на неблокирующем канале, и даже не проверяете записалось оно или нет.
Спектрум был неплохой машиной для игр и обучения программированию, но разрабатывать на нём и сразу на месте тестироваться было больно: ассемблер и исходники программы занимали память, которой и так было немного, плюс программа могла зависнуть, погубив среду разработки. Из-за этого для разработки использовали разные непростые и недешёвые варианты. Вот тут утверждают, что у Ultimate было что-то типа многопользовательской Unix-машины на 68000 с кросс-компиляцией. Эх, нигде нет деталей.
Согласен, насчёт константости - скорее всего враньё. А насчёт скорости: если посмотреть документацию на vers-vecs , то там написано
Забавная статья. Стиль автора, обещающий жареных фактов, никуда не делся, но хоть себя не нахваливает, уже лучше.
Ага, говорили. И это, конечно же, не было опровергнуто в статье.
Всё правильно Серёга написал, это только особенность языка, что вызов super() должен быть первым оператором в конструкторе, на уровне байт-кода это не проверяется.
Я проверил это так. Написал пару классов:
Скомпилировал, и через javap дизассемблировал. Конструктор класса Child выглядит вот так:
С jvm-ассемблерами оказалось много возни, поэтому я просто шестнадцатеричным редактором переставил байты, чтобы стало вот так:
И всё отлично работает.
До меня не сразу дошло, в чём суть проблемы Identity. Если нужно проверить, что две ссылки указывают на один и тот же объект через равенство, то им не обязательно быть именно ссылками на память, можно формировать их на основе полей объекта, и это сработает. Но проблема Identity состоит в том, что ссылки на два разных объекта с одними и теми же полями обязаны быть не равны.
Эта статья вызвала мой интерес не тем, что она про нейросети, а видом сжатого С-кода. Дело в том, что очень похожий стиль принят среди разработчиков на языках k и q, и интерпретаторы этих языков часто пишут в таком сжатом стиле, который мне даётся с трудом. Я подумал, что ну вот, может тут расскажут, как писать такое, поделятся секретами. А нифига, я сходил на github, и там исходники лежат в гораздо более понятном виде, и питоновский скрипт, который выполняет сжатие. Но при ближайшем рассмотрении оказалось, что питоновский скрипт делает довольно тупые действия, и автору в основном исходнике приходится самому прибегать к препроцессорной магии.
Когда я дошёл до раздела, где объясняется препроцессорная магия для матрично-скалярных и матрично-матричных операций, я сначала нифига не понимал, до тех пор, пока исходники не почитал. Дело в том, что в тексте вроде как define-ится BINARY с двумя аргументами (function и operation) в нижнем регистре, а потом в теле этого BINARY эти же аргументы стоят в верхнем регистре (FUNCTION и OPERATION), это очень сбивает с толку. Это не проблема перевода, в оригинальном тексте тоже так, в исходниках всё норм (fn и opr везде). Ну и раз уж зашла речь про BINARY, то я бы в начале проверил, что размеры матриц совпадают, fail fast и всё такое. Забавно, что он вводит поэлементное умножение и деление матриц, я такого не видел в математике, но у него находит применение в LayerNorm.
В описании математического матричного умножения у автора тоже косяк, в первом, неоптимизированном варианте остались k2 и j2.
Эта статья слишком коротка. Я с первого раза ничего не понял, рассуждения показались мне слишком абстрактными. Потом я взглянул на вашу библиотеку, и понял, что мне не показалось.
Я не настоящий хаскеллист, что такое функтор я понял, что такое монада - тоже понял, стрелку уже не осилил, а вот этих ваших абстракций спасибо не надо.
Что касается вашей исходной посылки про сложности предметных областей, то программирование не создано, чтобы бороться с этим.
Самый простой способ написать эмулятор и виртуальную машину - это написать интерпретатор, с центральным бесконечным циклом, внутри которого будет происходить выборка команд с последующим switch-ем по опкодам. В случае эмулятора нет смысла делать что-то другое (типа threaded code), потому что после каждой команды нужно проделать некоторые общие вещи: посчитать число тактов, проверить, не возникло ли прерывание, проэмулировать другие компоненты, помимо CPU. Кроме того, архитектура реальных машин такова, что код хранится в памяти, то есть может быть изменён в процессе выполнения, и стек тоже находится в памяти. Это значит, что JIT/AOT применять не получится. А виртуальная машина - она "виртуальная", потому что у неё, как правило, отдельно память данных, отдельно хранилище кода, отдельно локальные переменные, отдельно стек возвратов, отдельно стек операций. То есть высокоуровневые вещи, ограничивающие всякие трюки в коде, но позволяющие JIT/AOT и прочие оптимизации.
То есть для эмуляторов отказываться от цикла со switch-ем - это, с одной стороны, усложнение кода, а с другой стороны - вообще не нужно, всё равно скорость и задержки надо эмулировать.
Вообще не в тему комментарий. Эмулятор конкретного железа не является байткод-машиной, потому что должен повторять потактово время выполнения инструкций эмулируемого железа, нет никакой необходимости добиваться некоей "максимальной" производительности.
Отличная статья! Поймал себя на том, что смотрю на нули и единицы в закодированном представлении bitmap-а, и вижу мышку.
Хочу спросить про ассемблерную процедуру: вы в ней испортили HL и A, их не надо было сохранять и восстанавливать?
И про сам язык хотел спросить. Вы положили в массив и размеры битмапа, и сам битмап. А нет в языке поддержки struct, чтобы длину и ширину класть в именованные поля?
Я не понял, зачем это может быть нужно. Я не спец в системе модулей Scheme, и не понимаю, что такое "динамическая подгрузка" модулей, но разве модуль - это не набор define-ов, которые все одновременно попадают в область видимости? Если функция инициализации попала в область видимости, то и всё остальное содержимое модуля тоже попало.
Я догадываюсь, что это может быть полезно во всяких макросах.
По мере чтения статьи у меня возникали разные вопросы, но потом я сам находил на них ответы. Чтобы это не пропало, я это сюда в виде комментария сброшу.
Если я правильно понял, то идея демо такая: буфер не очищается, на каждом шаге в него добавляются белые точки, и потом проходит блюр. Наверное, при многократных исполнениях такого цикла изображение должно стремиться к белому. Но операция деления на 4 проходит с отбрасыванием дробной части, что, в свою очередь, даёт смещение в сторону чёрного. То есть подобрав количество добавляемых белых точек можно получить относительно стабильный баланс серого.
Я так понимаю, что способ определить начало буфера через добавление константы к сегментному регистру работает, потому что тупо есть органичение, что com-файлы не могут быть больше 64кб, поэтому нефиг заморачиваться.
В том цикле, который палитру создаёт, вам точно надо
mov ax, 1010h
делать каждый раз внутри цикла? Может вынести в начало?С блюром у вас занятно: для самого верхнего ряда вы обращаетесь и к предыдущим 320 байтам, которые вообще не в буфере. Кроме того, поскольку вы счётчик цикла инициализируете в 0000h, то цикл обработает 10000h байт, хотя достаточно обработать fa00h байт. Но, поскольку вы стремитесь к уменьшению размера программы, а не к скорости, то это понятно.
В цикле блюра можно было бы обойтись одним регистром, тем же SI. Я думаю, что inc si выставит флаг нуля, и вместо loop сделать переход через jnz. Если SI для этого не подойдёт, то его в цикле можно заменить на BX, он тоже умеет в адресацию со смещением.
Очень хорошая статья!
Не могли бы вы подтвердить ваш комментарий конкретикой: подробнее рассказать, какие именно легаси и костыли в IDEA ? Особенно интересно послушать про "кучу".
Пользуюсь IDEA уже дохреналлион лет, сижу на Community и буду продолжать. JetBrains - молодцы, делают отличный продукт. Spring boot знаю, но использую по минимуму, стараюсь избегать участия в разработке CRUD-сервисов. Плагин amplicode не использовал, чуть знаком: но на прошлой неделе в нашей организации был воркшоп, на котором этот плагин презентовал сотрудник фирмы-разработчика, очень толковый парень по имени Илья. Очень надеюсь, что мне не придётся писать сервисы, в котором этот плагин бы пригодился, все эти JPA, мапперы, контроллеры - ну тоска же зелёная! К инициативе по перепаковке чужого кода отношусь нейтрально, является ли это IDE, JDK или линукс. К заманухам про "бесплатность" и "открытость" отношусь скептически, потому что IDEA CE также бесплатна и открыта. Пользоваться перепакованной IDE не буду. Сообщества там нет и не будет. К автору статьи, который отмечается под каждым комментарием, отношусь отрицательно.
Если сотрудник в процессе работы чувствует, что недостаточно профессионален для выполнения задачи или вообще для должности/позиции, пришёл к руководителю признать этот факт и попросить помощи - то это высокая степень профессионализма. Статья же напоминает известные шутки "Ну вы же профессионал/коммунист".
Дрон ощущается хорошо, с ним хорошо придумано. Пейзажи выглядят норм, реалистично, а персонажи выглядят и анимированы очень нереалистично, это очень заметно на фоне пейзажей.
Я позавчера поиграл в демо, мне понравилось, автор молодец. Головоломки несложные, атмосфера серой тоскливой зимы передана хорошо. Многие игры делают вид, что у них сложный мир, оригинальный сюжет и им есть что сказать, и эта игра не исключение, но я уже привык. Свободы исследования нет, всё линейно, стенки невидимые кругом и двери неоткрывающиеся.