All streams
Search
Write a publication
Pull to refresh
71
0
Dzmitry Malyshau @kvark

User

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

И раз уж мы затронули эту злободневную тему, не расскажете ли преимущества «бутсрапинга или кодогенерации» перед нормальными обобщениями?
У нас тут не портал начинающих писателей, знаете ли, а технический ресурс. Автор виновен не только в бранности, но прежде всего — в несостоятельности своей позиции. Nim — замечательный язык, надежда многих разработчиков на хорошую смесь C++ и Питоном, но говорить о продуктивности здесь и сейчас как-то странно, учитывая нестабильность языка и неразвитость экосистемы.
Quick Sort, который на данный момент не поддается распараллеливанию

А можно здесь подробнее? Quick Sort ведь классический пример подхода «разделяй и властвуй», который в целом неплохо распараллеливается: разделил массив на 2 части (бОльшую и меньшую, чем якорь) и обрабатывай их параллельно, каждая часть в итоге может ветвиться точно также.

В «классической» реализации Radix Sort хранить значения предлагается в самом radix массиве, т.е. каждый элемент это тоже массив, в который записываются исходные числа, как раз здесь скрыта причина высокого расхода памяти.

Это где вы такую реализацию видели? O_o

Если позиция копирования числа уже занята, то ищем первый не занятый элемент в итоговом массиве.

И далеко вы собираетесь идти искать?

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

Как ваш способ соотносится со стандартной реализацией?

Возможность параллельного вычисления

Не очевидно это. Как вы собираетесь распараллеливать?

Локальность данных при обработке — Достаточная

Финальная фаза, где заполняется результирующий массив, пишет элементы в разные участки памяти на каждой итерации. Трудно придумать сценарий хуже. Вы как-бы и сами пишете, что доступ к памяти «Умеренно случайный», что намекает.
Про 10 лет, Вы, конечно, преувеличили. Но вот «прямо сейчас», о чём так настойчиво говорит автор, ехать на этом феррари как-то рискованно, учитывая, что двигатель и колёса могут ещё пару раз заменить прямо на ходу.
Так… автор же об этом и пишет в последующем абзаце:
то для людей создающих платформу, на которой предполагается одновременный запуск сотен плагинов, различных версий и от десятка не знающих друг о друге вендоров любая лишняя зависимость — зло
Простите, но тут сплошной разброд и шатание, а не статья. Автор бегает вокруг, брюзжет на всё подряд, попутно не забывая показать свой многолетний опыт и навыки.

Слово «продуктивность» упоминается чуть ли не в каждом предложении, в то время как качеству, надёжности и производительности почти не уделено внимание (типа, железо уже сильное, не парьтесь). Создаётся вполне определённое впечатление о том, какого рода код пишет автор и его команда. Это код, измеряемый строчками в секунду… Быстрее, больше, ещё больше!
Хорошо написано! Пару слов меня одёрнули, впрочем («первоманс», «заимитить»), но это мелочи.

По представлению карты теней для всенаправленного источника света — помимо cube map и dual-paraboloid есть ещё такая интересная экзотика как тетраэдральные карты (tetrahedral shadow maps). Они требуют 4 клона геометрии вместо 6 и при этом не искажают результат как параболоиды. К сожалению, данный подход недостаточно изучен до сих пор, трудно найти приличные примеры кода.

Про depth bias — в DirectX 9 он ведь тоже был, пусть и в несколько ином формате. В принципе, ничего не мешало его использовать там, просто для подсчёта абсолютного смещения нужно было учитывать разрядность карты глубины.
Думаю, можно и без плагина обойтись. Просто try_block! как новый макрос кажется вполне возможным.
Дьявол, как говорят, — в деталях. Дело не в языке, а в конкретных программах, до которых ещё не добрались оптимизаторы. По факту там яблоки с грушами сравнивают. На GСС используют SSE, OpenMP и явную многозадачность. На Rust код как из учебника (740 символов на regex-dna против 2579 на С).

Единственный пример, до которого «добрались» — k-nucleotide — почему-то рвёт GCC и по скорости, и по памяти, и по размеру кода. Так что я не вижу в этих цифрах проблемы, всё образуется ;)
Спасибо! Надеюсь, это развеит сомнения по поводу скорости в Rust.

Имейте ввиду, что основной целью последнего чуть ли не кода разработки была стабилизация API. Оптимизация же не была приоритетна, но ей обязательно займутся сейчас, после 1.0, не нарушая интерфейсов. Уверен, у разработчиков ещё есть пара спрятаных козырей, которые позволят ещё более разогнать язык.
Пусть так, но моё утверждение от этого слабее не становится. Читаем обработку ошибок в Go, видим IO методы, возвращающие пару (результат, ошибка):

func Open(name string) (file *File, err error)
...
f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}

Насколько я вижу, ничто не запрещает использовать «f» несмотря на возникшую ошибку.
Программа либо устанавливала флаг ошибки, либо возвращала код, который проверялся вызывающей стороной.

У обоих решений имеется общий недостаток: они загромождают код на стороне вызова. Вызывающая сторона должна проверять ошибки немедленно после вызова. К сожалению об этом легко забыть.

Кажется, автор не принимал во внимание алгебраические типы. С ними невозможно «забыть» проверить ошибки, а добавление try! (в простом случае) не особо нагромождает код:

let x = try!(foo());
bar(x);

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

Сомнительное повышение качества. Хорошо разделять системы, работающие независимо друг от друга. Здесь же — обработка ошибок непосредственно влияет на алгоритм (прерывая его), а алгоритм — на обработку ошибок (требование сохранять инварианты).
То есть ссылку от peek() Вы не используете? В таком случае, можно было и так:

while iter.peek().is_some() {
   println!("{}", iter.next());
}
Вы просто в примере написали println!("{}", it.next()), что явно указывает, что it — итератор. Чем всё-таки не подходит мой пример с while/next?

Стандартный способ — peek/match/next.

Это где так, в Kotlin?

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

while let Ok(msg) = receiver.try_recv() {
   println!("{}", msg);
}
1) Обещали стабильность самого языка, а не стандартной библиотеки. Как правильно, нестабильные методы являются вторичными. К примеру, вместо vec.push_all() можно использовать более универсальный vec.extend()

2) Не вижу в этом ошибки механизма. Что такое it у Вас? Если это другой итератор (тогда iter — итератор над итераторами?), то он должен быть изменяемым для вызова it.next(), в то время как peek() позволяет только подсмотреть следующее значение, но никак не поменять его.

Полагаю, на самом деле Вы хотели так:

while let Some(val) = iter.next() {
   println!("{}", val);
}


3) Смотрим на Peekable, видим структуру с параметром I: Iterator. Ваша функция, следовательно, может выглядеть так:

fn func<I: Iterator>(iter: &mut Peekable<I>) -> String

Документация описывает where и приводит ряд примеров.
Спасибо за информацию! На всякий случай уточните, собирали ли Вы с включённой оптимизацией.

Как тестировали? Могу ли я воспроизвести данный тест у себя на компьютере?

Nickel — не единственный вариант. Вот тут можно увидеть альтернативы — arewewebyet.com
А можно ссылку на форумы или куда-нибудь ещё, где ситуация описывается? Не может быть Rust настолько медленнее. Вы в release собирали? Простите за банальный вопрос :) Никогда не слышал о нерешаемых проблемах с производительностью в Rust.
Я понимаю, о чём Вы, но не совсем разделяю. Допустим, у вас есть такой код внутри функции:

foo(x);
bar(y);

Спаведливо (интуитивно? очевидно?) полагать, что сначала вызовется foo, а потом bar, как и написано. Однако, исключение в foo, которое здесь не отлавливается, может сорвать вызов bar. Таким образом, может быть нарушена целостность объекта, инвариант состояния, и т. д. (допустим, разрыв пары lock/unlock).

Чтобы понять, что произойдёт в действительности, программисту нужно знать, какие исключения и при каких условиях бросают foo и bar, что уже совсем не очевидно.
Да, если бы не Яист, Оберон уже давно бы потеснил С с С++, ага.
Автор, прошу Вас, меньше воды и капризов, больше практики и глубины.

Information

Rating
Does not participate
Location
Toronto, Ontario, Канада
Date of birth
Registered
Activity