Редактирование истории в гите довольно запутано. Пользоваться git rebase/git cherry-pick может быть довольно тяжело. Или условно: разделить коммит в середине длинной ветки на два.
Работа со stash и другими грязными изменениями. В гите требуется вручную разобраться со всеми незакоммиченными изменениями, прежде чем выйдет переключиться на другую ветку (например, быстро глянуть, а что там на мастере)
Итого, jj делает редактирование истории более доступным.
Ну и в целом, семантическая модель «коммитов» jj гораздо ближе к представлению большинства пользователей, чем семантическая модель в гите. Чаще всего, люди плохо себе представляют, что происходит с графом коммитов, когда мы, например, удаляем один коммит из середины истории. Многие не понимают (на уровне интуиции), зачем нужен git gc.
Был какой-то симулятор/обучалка гита, в котором была визуализация всего графа коммитов. По опыту, когда люди в ней делали git rebase - они думали, что всё сломалось.
P.S. Рассуждения про семантическую модель основаны на малой выборке, а потому субъективны. Будет интересно соотнести их с опытом других людей.
P.P.S. Две проблемы обозначенные вначале - объективны.
# Нативная библиотека, реализация ниже
from nativelib import magic, run_with_cookie
# Здесь подконтрольный код, его можно как угодно преобразовать
# Как угодно
s1, s2, s3, s4, s5 = ... # сокеты
# Вызываем нативную функцию из питона
magic(42, lambda: s1.read())
magic(10, lambda: s2.read())
# "горутина" со чтением
go:
s3.read()
run_with_cookie(12, lambda: s4.read())
# Заблокируем главный поток на мютексе
run_with_cookie(0, lambda: s5.read())
//! Реализация нативной библиотеки на псевдокоде
//! Изменить эту библиотеку нельзя, потому что:
//! 1) Она реализована на другом языке
//! 2) Она скомпилирована в бинарник и выложена на pypi
pub fn magic(cookie, cb) {
thread::spawn(|| {
run_with_cookie(cookie, cb);
});
return;
}
pub fn run_cookie(cookie, cb) {
// Обычный системный мютекс, блокирует системные потоки
// Про горутины ничего не знает
global_mutex.lock();
global_var = cookie;
cb();
assert!(global_var == cookie);
global_mutex.unlock();
}
Вопрос в том, как это будет исполняться? Допустим, что у нас всего один реальный поток в интерпретаторе.
В частности, что произойдёт, когда cb() наткнётся на s1.read() - по Вашим словам, он должен сделать yield. Это, в свою очередь, остановит исполнение потока и запустит на этом же потоке другую горутину. Что будет, если другая горутина - это go с s3, s4? Как они себя будут вести?
UB не путешествует во времени. Просто поведение логических выводов компилятора для программы, допускающей UB, может быть неочевидно для программиста.
Я понимаю, почему это происходит. Но я говорю об эффектах: наличие ub в программе может поменять её поведение, даже до его возникновения.
тогда и impementation-defined должен себя вести так же, см. различие только в требовании стандарта о документации поведения.
Собственно, наличие документации всё меняет. Например, если документация говорит: «results in two-complement overflow», то замена станет некорректной. А вот если говорит: «may result in any valid int value, or crash the program», то такая замена всё ещё оправдана.
В случае unspecified behaviour, приходится полагаться на худший случай.
P.S. Я скорее говорю о UB и прочем как о концептах построения языков и компиляторов. Если же разговор был про UB конкретно в стандарте плюсов - там немного другой разговор.
Нет, потому что это ещё и зависит от трейса, который привёл к коду.
Например, какая-нибудь функция до нас могла записать адрес I в глобальную переменную, или вообще переслать в другой поток. Активация той функции уже закончилась, но…
Плюс обращу внимание, что это потребует всего кода процесса (т.е. мы запрещаем шареные либы, unfork, CreateRemoteThread и прочие системные вызовы), а также анализ псевдонимов, вроде, делается за экспоненциальное время…
Доступ к кешу имеет latency 4 цикла. С другой стороны, там взбухнет таблица переходов: больше нагрузка на icache и усложниться работа предсказателя ветвлений. Для меня неочевидно, что будет быстрее.@qw1 ответил Вам здесь
Возможно стоит посмотреть на компромисс: хранить в регистрах только два верхних элемента стека. Условно:
Также, если мне не изменяет память, то 3-4 регистра - оптимально для большинства кода на стэковой машине. Наращивать больше не очень эффективно. Но это, в основном учитывает арифметику; возможно специфика форта сдвинет данный оптимум.
Это неатомарное чтение. Оно невозможно в Яве. Даже для long-ов на 64битном компьютере.
Источником неатомарный чтений является не совсем кеш, а скорее отсутствие (атомарных) машинных инструкций на чтение/запись достаточной ширины. Но для Явы это неважно, потому что спека требует атомарности всех чтений (примитивов).
Не очень понимаю, почему. У Вас же тогда может быть активны две критических секции одновременно: на разный нитях одного потока ОС. Или я Вас неправильно понимаю?
Edit: не в ту ветку, отвечал на вопрос @event1
Решается две проблемы пользователей:
Редактирование истории в гите довольно запутано. Пользоваться git rebase/git cherry-pick может быть довольно тяжело. Или условно: разделить коммит в середине длинной ветки на два.
Работа со stash и другими грязными изменениями. В гите требуется вручную разобраться со всеми незакоммиченными изменениями, прежде чем выйдет переключиться на другую ветку (например, быстро глянуть, а что там на мастере)
Итого, jj делает редактирование истории более доступным.
Ну и в целом, семантическая модель «коммитов» jj гораздо ближе к представлению большинства пользователей, чем семантическая модель в гите. Чаще всего, люди плохо себе представляют, что происходит с графом коммитов, когда мы, например, удаляем один коммит из середины истории. Многие не понимают (на уровне интуиции), зачем нужен git gc.
Был какой-то симулятор/обучалка гита, в котором была визуализация всего графа коммитов. По опыту, когда люди в ней делали git rebase - они думали, что всё сломалось.
P.S. Рассуждения про семантическую модель основаны на малой выборке, а потому субъективны. Будет интересно соотнести их с опытом других людей.
P.P.S. Две проблемы обозначенные вначале - объективны.
Пример:
Вопрос в том, как это будет исполняться? Допустим, что у нас всего один реальный поток в интерпретаторе.
В частности, что произойдёт, когда cb() наткнётся на s1.read() - по Вашим словам, он должен сделать yield. Это, в свою очередь, остановит исполнение потока и запустит на этом же потоке другую горутину. Что будет, если другая горутина - это go с s3, s4? Как они себя будут вести?
Я бы сказал что теория множеств в упоминаемом контексте скорее вредна: это слишком конкретная модель, у неё можно «провзаимодействовать с границей».
А чем это так показательно?
Про перемещение во времени мы говорим об одном и том же. Не вижу смысла продолжать.
Не понял. Перефразируете?
Ну тогда реальные консервативные компиляторы без ub не умеют ничего доказывать.
Не очень понимаю. Вы предлагаете делать глобальный анализ?
Я понимаю, почему это происходит. Но я говорю об эффектах: наличие ub в программе может поменять её поведение, даже до его возникновения.
Собственно, наличие документации всё меняет. Например, если документация говорит: «results in two-complement overflow», то замена станет некорректной. А вот если говорит: «may result in any valid int value, or crash the program», то такая замена всё ещё оправдана.
В случае unspecified behaviour, приходится полагаться на худший случай.
P.S. Я скорее говорю о UB и прочем как о концептах построения языков и компиляторов. Если же разговор был про UB конкретно в стандарте плюсов - там немного другой разговор.
Если я правильно понимаю актуальную трактовку компиляторщиками, то поведение, как я описал.
Главное отличие ub - оно путешествует во времени. Т.е. программа может начать вести себя странно, ещё до того, как «произойдёт ub».
Если попробуете определить, что это значит, то окажется, что замена на op2 может быть корректной, или нет - по желанию реализации.
Но это всё эквилибристика. Реально нужно смотреть на то, как это компиляторщики интерпретируют.
Если переполнение unspecified, то замена будет корректной. Даже если в каких-то случаях op2 падает.
Переполнение делают ub, чтобы адекватно анализировать пересечения циклов и пересечения указателей.
Нет, потому что это ещё и зависит от трейса, который привёл к коду.
Например, какая-нибудь функция до нас могла записать адрес I в глобальную переменную, или вообще переслать в другой поток. Активация той функции уже закончилась, но…
Плюс обращу внимание, что это потребует всего кода процесса (т.е. мы запрещаем шареные либы, unfork, CreateRemoteThread и прочие системные вызовы), а также анализ псевдонимов, вроде, делается за экспоненциальное время…
Доступ к кешу имеет latency 4 цикла. С другой стороны, там взбухнет таблица переходов: больше нагрузка на icache и усложниться работа предсказателя ветвлений. Для меня неочевидно, что будет быстрее.@qw1 ответил Вам здесь
Возможно стоит посмотреть на компромисс: хранить в регистрах только два верхних элемента стека. Условно:
Такой подход, кажется более щадящим.
Также, если мне не изменяет память, то 3-4 регистра - оптимально для большинства кода на стэковой машине. Наращивать больше не очень эффективно. Но это, в основном учитывает арифметику; возможно специфика форта сдвинет данный оптимум.
Кажется, нагрузка на цикл декодирования будет выше, профита от такой оптимизации.
Скорее создаёт новое имя, для существующего объекта. Чтобы создать место, имя нужно запинить.
Это неатомарное чтение. Оно невозможно в Яве. Даже для long-ов на 64битном компьютере.
Источником неатомарный чтений является не совсем кеш, а скорее отсутствие (атомарных) машинных инструкций на чтение/запись достаточной ширины. Но для Явы это неважно, потому что спека требует атомарности всех чтений (примитивов).
Не очень понимаю, почему. У Вас же тогда может быть активны две критических секции одновременно: на разный нитях одного потока ОС. Или я Вас неправильно понимаю?