Как стать автором
Обновить
31
0
Григорий Наследов @dougrinch

Пользователь

Отправить сообщение
Утилита javah больше не нужна, потому что нативные заголовки теперь может делать javac

Очень интересно. Я правильно понимаю, что если у меня сорцы на котлине, то теперь хрен мне, а не jni? Спасибо тебе, Оракл, что заставляешь в полностью котлиновском проекте заводить единственный джавовый класс со всеми нативными методами.
Если я правильно помню, то в идее тоже сразу редактируется не файлы на диске, а некоторое внутреннее представление. Рассказывали в каком-то докладе что основной паттерн для иде — огромное количество маленьких файлов. И для диска это плохо. И внутри используется один гигантский файл — кэш. Так что у эклипса явно другая проблема — изменения не скидываются на диск. В идее сброс на диск производится не только при сохранении файла, но и просто при смене фокуса с иде на что-то другое. А при обратной фокусировке диск сканируется на наличие изменений.
Как вам совершенно правильно ответил yole, основная причина вообще не в производительности, а чтобы можно было return написать.
Смотря как посмотреть. Если строго формально, то да, вы правы. Но только с тем же успехом можно утверждать что удаление из хэшсета у нас O(n!). Не, ну а чё? Константа действительно растет не быстрее факториала. Только есть одна проблема — данный ответ, не смотря на свою абсолютную правильность, не несет абсолютно никакой пользы. Так что для практического использования важно брать не просто верхнюю оценку, а «близкую» верхнюю оценку. И вот здесь допустимость замены sum(2,n,log^2(x)) -> nlog^2(n) уже не так очевидна. (сорри, на знаю как красиво формулы писать сюда)
Очевидное предположение, но хотелось проверить. Да, именно они. У меня получается их 16-19кк обычно. По 40 байт на каждый. 840кк все равно не получается, но тут уже близко очень. Может не везет просто. Ладно, я всё. Дальнейший анализ оставляю вам.

Ну так я же не сам придумал. Честно, был безумно удивлен когда это обнаружил. На что именно уходит память не анализировал еще. Получается что QuickSort и Arrays.sort не выделяют ничего. Arrays.parallelSort выделяет 400кк байт — это ровно еще один наш массив интов (и там действительно внутри есть new int[n]). А вот стримы (независимо от параллельности) и ParallelQuickSort выделяют дополнительно 800кк.


Более того, в стримах результат очень стабильный и там 800кк почти ровно.


Benchmark                            Mode  Cnt          Score            Error   Units
SortBench.sort:·gc.alloc.rate.norm   avgt    5  800036601.600 ±        839.235    B/op

А вот в ParallelQuickSort какой-то расколбас.


Benchmark                            Mode  Cnt          Score            Error   Units
SortBench.sort:·gc.alloc.rate.norm   avgt    5  840529246.400 ±    126746.361    B/op

Надо через jmc посмотреть что там происходит.

Вот что нашел при быстром поиске (возможно есть еще что-то, но не копал уже).

1. В java-сообществе уже давно публиковать бенчи не на jmh считается неприлично.
2. Ваш вариант с int v = a[lo]; все-таки уж очень плохой. Если запустить повторную сортировку на массиве из примера, то ее результата мы уже никогда не дождемся. Банальная замена на int v = a[ThreadLocalRandom.current().nextInt(lo, hi + 1)]; полностью все исправляет.
3. Сравнение со стримами тоже плохое. Вы сортируете массив на месте, а стримы создают новый не трогая исходный.
4. В связи с последним особенно интересно что памяти ваш вариант выделяет столько же, сколько и стримы.

Но вообще очень показательно получилось. Очень хорошо видно что на таком кол-ве данных в первую очередь важна именно асимптотика, и не смотря на все отличия/оптимизации в решении, итоговый результат зависит от них довольно мало.
Я, конечно, тоже молодец, слишком доверчивый. Раз написано про ошибку в композиции, то проверил именно ее. А получилось что это как раз единственный закон из трех, который работает полностью.

Но вообще, мне кажется что все это некорректно. Если за базу мы берем haskell, то с нулами получается интересно. Ведь там их нет, именно за этим и нужен Maybe. Соответственно, логично предположить что если функция принимает какой-то тип, то она принимает именно этот тип, а не null. И тогда получается что (return v) >>= f ≡ f v для случая v = null просто не имеет смысла, т.к. конструкции (return null) и (f null) не существуют. На простой map, я думаю, данное ограничение тоже распространяется.

Так что еще большой вопрос является ли Optional монадой в строгом значении.

Но вообще, по поводу монад у меня есть другая мысль. Рискну быть заминусованным, но предположу что когда про монады говорит кто-нибудь, кто не является профессиональным фп-программистом (что-бы это не значило), он имеет в виду не строгий математический объект, а некую monad-like «контейнерную» абстракцию, позволяющую легко работать напрямую с содержимым в «нормальных» сценариях. А что при этом оно почти всегда разваливается на границах — да кому какое дело?! В конце концов, не надо мешать нулы и опшионалы.
Распознают, и в профиль заглядывают, и даже видят там уже поставленный плюс в карму. Но конкретно этот коммент, что называется, «слишком толстый». И парсится уже не как шутка, а как глупое отвлечение от темы.

p.s. Сам пост отличный, спасибо!
Простой тест с перебором всех возможных значений показывает что разный результат будет только когда m == empty() && (f == null || g == null). Честно говоря, мне кажется что тестирование монадических законов на нулевых функциях — это читерство и так делать нельзя.

сам тест
public class MonadTest {

    public static void main(String[] args) {
        List<Optional<String>> ms = asList(ofNullable(null), ofNullable("123"), null);
        List<Function<String, Optional<Integer>>> fs = asList(s -> ofNullable(null), s -> ofNullable(s.length()), s -> null, null);
        List<Function<Integer, Optional<Boolean>>> gs = asList(s -> ofNullable(null), i -> ofNullable(i.intValue() == 0), i -> null, null);

        for (int i = 0; i < ms.size(); i++) {
            Optional<String> m = ms.get(i);
            for (int j = 0; j < fs.size(); j++) {
                Function<String, Optional<Integer>> f = fs.get(j);
                for (int k = 0; k < gs.size(); k++) {
                    Function<Integer, Optional<Boolean>> g = gs.get(k);
                    try {
                        test(m, f, g);
                    } catch (AssertionError e) {
                        System.out.println(i + " " + j + " " + k);
                    }
                }
            }
        }
    }

    private static void test(Optional<String> m, Function<String, Optional<Integer>> f, Function<Integer, Optional<Boolean>> g) {
        Optional<Boolean> left;
        boolean npeOnLeft;
        Optional<Boolean> right;
        boolean npeOnRight;

        try {
            left = m.flatMap(f).flatMap(g);
            npeOnLeft = false;
        } catch (NullPointerException e) {
            left = null;
            npeOnLeft = true;
        }

        try {
            right = m.flatMap(x -> f.apply(x).flatMap(g));
            npeOnRight = false;
        } catch (NullPointerException e) {
            right = null;
            npeOnRight = true;
        }

        assertEquals(npeOnLeft, npeOnRight);
        if (!npeOnLeft)
            assertEquals(left, right);
    }
}

Почему не соблюдается? Вроде нормально же
Optional<String> m;
Function<String, Optional<Integer>> f;
Function<Integer, Optional<Boolean>> g;

Optional<Boolean> left = m.flatMap(f).flatMap(g);
Optional<Boolean> right = m.flatMap(x -> f.apply(x).flatMap(g));
А, блин, лоханулся я. Это же фича джава компилятора будет, так что надо еще допиливать котлиновский чтобы он про Intrinsics.invokedynamic тоже узнал. А если в любом случае надо его пилить, то и JEP-303 уже можно не ждать и сделать свой велосипед.
Во!
    public static CallSite bootstrapLoggerFactory(MethodHandles.Lookup lookup, String name, MethodType type) {
        Logger logger = LoggerFactory.getLogger(lookup.lookupClass());
        MethodHandle mh = MethodHandles.constant(Logger.class, logger);
        return new ConstantCallSite(mh);
    }
Ну да, иначе никак не получается сейчас
val public_strange_name_do_not_use_it = ConcurrentHashMap<String, Logger>()

inline val logger: Logger
    get() {
        return public_strange_name_do_not_use_it.computeIfAbsent(Exception().stackTrace[0].className) { cn -> LoggerFactory.getLogger(Class.forName(cn)) }
    }


По получаемому синтаксису вроде идеально получается, здесь только с перформансом уже проблемы скорее всего будут (по крайней мере, для библиотечной функции общего пользование такое явно недопустимо). В принципе, если дождаться JEP-303, то в паре с инлайном это будет идеальное решение. По идее, тогда даже мапа будет не нужна, т.к. можно будет повесить CallSite напрямую на нужный логгер, но в этом я пока не уверен, надо внимательнее посмотреть.
Если честно, то лень. Но это и есть весь код. Суете в либу на топ уровне что я писал ранее, а используется просто как
import org.dou.lib.logger

class Foo {
    init {
        logger.info("hello") //[main] INFO org.dou.Foo - hello
    }
}


На самом деле, я уже обнаружил две большие проблемы у этого варианта:
1. Это публичное экстеншн проперти, так что я «logger» могу написать не только внутри класса, но и снаружи
42.logger.info("msg") //[main] INFO java.lang.Integer - msg

2. Он вообще не работает с топ левел функциями (вне класса/объекта). Т.к. там нет this, у которого logger и отрастает.

В принципе, обе проблемы решаются если убрать «Any.» и сделать просто
val logger: Logger get() { TODO() }
(тоже на рутовом уровне), но здесь я пока не придумал как в теле геттера получать класс, откуда он вызывается. Каждый раз через исключение смотреть стектрейс — явно плохо.
Так наоборот же. Эту штуку надо написать всего один раз (и можно даже в библиотеке) и всё! Вам больше вообще ничего делать не надо. У всех классов уже есть логгер. В отличии от ломбока, в котором как раз надо еще целую аннотацию поставить.

А чем плох такой вариант?


val Any.logger: Logger by object : ReadOnlyProperty<Any, Logger> {
    val loggers = ConcurrentHashMap<Class<*>, Logger>()
    override fun getValue(thisRef: Any, property: KProperty<*>): Logger {
        return loggers.computeIfAbsent(thisRef.javaClass, LoggerFactory::getLogger)
    }
}

Да, не static final, но неужели не пофиг?

Нет, Ceylon ни разу не является достойной альтернативой Kotlin.

Кстати, еще в общую копилку: This War of Mine. Из той же серии — ни черта не понятно, но эмоции и сильнейшее желание узнать об игре побольше вызывает.

Он на самом деле статический.

Информация

В рейтинге
Не участвует
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Дата рождения
Зарегистрирован
Активность