Pull to refresh

Comments 33

Скрытый текст
Вопрос третий
Можно ли в Java создать класс (именно класс, не интерфейс) без единого конструктора, даже приватного?


На хабре целая статья про этот изврат была, нагуглят быстро :)
Не экземпляр класса создать, а сам класс такой, что Class.getDeclaredConstructors() вернёт для него пустой массив.
Скрытый текст
1. Math.random использует обычный Random, который очень плохо работает в параллельном режиме. Использовать его с parallelSetAll имеет мало смысла: будет высокий контеншн. Нужен ThreadLocalRandom

3.
public class NoConstructor {
  private static class Nested {
    private Nested() {}
  }

  public static void main(String[] args) throws Exception {
    new Nested();
    System.out.println(Class.forName("NoConstructor$1").getDeclaredConstructors().length);
  }
}

$ javac NoConstructor.java

$ java NoConstructor
0

Работает в javac, не работает в ecj. Я про это писал здесь. Второй вариант — сгенерировать класс на лету, например, с помощью ASM, как это сделал LSD.

Над вторым ща подумаю.
Скрытый текст
Во втором для приватного метода javac сгенерирует аксессор, который увеличит глубину вложенности вызовов на один. Видимо, максимальная глубина инлайна ограничена 10. Пытаюсь найти опцию JVM, которая это контролирует.
Скрытый текст
Ну да, предположение правильное. С +PrintCompilation +PrintInlining вижу такое:
    210  263       3       org.sample.Modifiers::testGetPrivate (7 bytes)
                              @ 3   org.sample.Modifiers$Inner::access$000 (5 bytes)
                                @ 1   org.sample.Modifiers$Inner::getPrivate (5 bytes)
                                  @ 1   org.sample.Modifiers$Inner::getX9 (5 bytes)
                                    @ 1   org.sample.Modifiers$Inner::getX8 (5 bytes)
                                      @ 1   org.sample.Modifiers$Inner::getX7 (5 bytes)
                                        @ 1   org.sample.Modifiers$Inner::getX6 (5 bytes)
                                          @ 1   org.sample.Modifiers$Inner::getX5 (5 bytes)
                                            @ 1   org.sample.Modifiers$Inner::getX4 (5 bytes)
                                              @ 1   org.sample.Modifiers$Inner::getX3 (5 bytes)
                                                @ 1   org.sample.Modifiers$Inner::getX2 (5 bytes)
                                                  @ 1   org.sample.Modifiers$Inner::getX1 (5 bytes)   inlining too deep

А вот getPublic пролазит:

    168  261       3       org.sample.Modifiers::testGetPublic (7 bytes)
                              @ 3   org.sample.Modifiers$Inner::getPublic (5 bytes)
                                @ 1   org.sample.Modifiers$Inner::getX9 (5 bytes)
                                  @ 1   org.sample.Modifiers$Inner::getX8 (5 bytes)
                                    @ 1   org.sample.Modifiers$Inner::getX7 (5 bytes)
                                      @ 1   org.sample.Modifiers$Inner::getX6 (5 bytes)
                                        @ 1   org.sample.Modifiers$Inner::getX5 (5 bytes)
                                          @ 1   org.sample.Modifiers$Inner::getX4 (5 bytes)
                                            @ 1   org.sample.Modifiers$Inner::getX3 (5 bytes)
                                              @ 1   org.sample.Modifiers$Inner::getX2 (5 bytes)
                                                @ 1   org.sample.Modifiers$Inner::getX1 (5 bytes)

Результат
И ты становишься победителем!!! Лови нас с Андреем apangin, мы тебе вручим приз на открытии JPoint!
Скрытый текст
Вот, нашёл. Что-то не сразу нагуглилось -XX:MaxInlineLevel=11 практически уравнивает результаты. getPrivate у меня всё ещё несколько медленнее, но в пределах доверительного интервала.
Первая задача
Не джавист, но первая мысль при взгляде на
public static double[] getRandomVector(int size) {
        double[] vector = new double[size];
        Arrays.parallelSetAll(vector, i -> Math.random());
        return vector;
    }

о том, что скорее всего Math.random() в java не очень хорош в concurrency, о чём можно почитать в документации и убедиться. Соответсвенно, использовать ThreadLocalRandom
Мои непричёсанные мысли
1. Причина в том, что вся эта штука работает, по сути, последовательно (будет затор у Math.random()). Как сделать правильно и параллельно — сказать не могу, потому что не знаю, как сделать несколько датчиков случайных чисел, инициализированных чем-то более-менее независимым. Думаю, даже если сделать код простым последовательным, всё равно будет быстрее.

2. Точно не в курсе, но, думаю, какое-то хитрое поведение оптимизатора, который понимает, что getPublic() можно свернуть в return x; а getPrivate() — почему-то нет.

3. На уровне байткода, разумеется, можно. На уровне языка… думаю, что-нибудь наподобие внутреннего private abstract без нестатических данных.
Ответ
все три мысли — в правильном направлении! Копайте дальше!
Я подсмотрел правильные ответы.

Заголовок спойлера
По поводу первого — надо смотреть реализацию threadLocalRandom. Сработает-то сработает, но насколько качественны случайные числа, которые оно выдаст?

Второе — ключи компилятора. В такие дебри не совался.

А третье — просто не было времени запустить NetBeans и посмотреть.


З.Ы. Если сработало, да не так — задача не решена.
Вот на вчерашней Никитиной задаче я затупил, хотя про слоты всё знал. Ну да, сейчас понял. Достаточно первый пример модифицировать вот так, и он заработает:

public class OOM1 {
    private static final int SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.55);

    public static void main(String[] args) {
        {
            byte[] bytes = new byte[SIZE];
            System.out.println(bytes.length);
        }
        byte[] bytes1 = null;

        bytes1 = new byte[SIZE];
        System.out.println(bytes1.length);

        System.out.println("I allocated memory successfully");
    }
}


Мораль: пойду на доклад к Никите :D
ты там осторожно, а то упоритесь вместе по хардкору и натворите чего-нибудь не того!
Спасибо что теперь хоть по Java, без всяких там Springов Groovyей!

Мой ответы
По первой задачи Math.random() синхронизируется внутри, не очень подходит для параллелизации. Видимо нужно задействовать ThreadLocalRandom().

Во второй задаче — всё просто, вызов private будет обёрнут ещё в access метод, соответственно лишних overhead.

По третьей — тут уж я не такой знаток. Наверняка можно какой-нибудь библиотекой кодогенерации так сделать. Получить список констркторов, все удалить и сохранить. А вот чтобы из Java. Немного гуглежа и я нашёл обсуждение на StackOveflow — да, javac добавляет конструктор по умолчанию, это положено по спецификации. А при создании java байткода вручную таких ограничений нет, спецификация класс-файла не обязывает иметь конструктор.
Комментарии
  1. Не совсем. Никаких synchronized и локов внутри Math.random нет.
  2. Действительно, будет метод-аксессор, но JIT ведь умеет простые методы инлайнить, чтоб не было никакого оверхеда.
  3. Да, всё так.
Заголовок спойлера
1 — параллельное заполнение массива будет медленным не столько из-за Random, сколько из-за кэша процессора.

2 — Разница в том, что для private метода генерируется специальный метод access (предполагаю, т.к. внутренний класс хранится в отдельном class файле, иначе никак):

  public int testGetPrivate();
    Code:
       0: getstatic     #2                  // Field inner:LModifiers$Inner;
       3: invokestatic  #3                  // Method Modifiers$Inner.access$000:(LModifiers$Inner;)I
       6: ireturn

  public int testGetPublic();
    Code:
       0: getstatic     #2                  // Field inner:LModifiers$Inner;
       3: invokevirtual #4                  // Method Modifiers$Inner.getPublic:()I
       6: ireturn
.....
static int access$000(Modifiers$Inner);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method getPrivate:()I
       4: ireturn



3 — не хочу даже думать над этим извратом, один вопрос — зачем???

1. Поясните, что вы имеете ввиду
Заголовок спойлера
1 — я думал сперва про spatial locality (подумал, тут подвох и не может быть задача на просто знание JavaDoc).

Тесты заставили меня поменять свои взгляды gist.github.com/relgames/840d89280237acd4c385

Benchmark                                  (size)  Mode  Cnt        Score        Error  Units
MyBenchmark.randomParallel                    100  avgt    5       20.629 ±      1.920  us/op
MyBenchmark.randomSequential                  100  avgt    5        2.996 ±      0.569  us/op
MyBenchmark.threadLocalRandomParallel         100  avgt    5        4.336 ±      0.172  us/op
MyBenchmark.threadLocalRandomSequential       100  avgt    5        0.351 ±      0.086  us/op

MyBenchmark.randomParallel                   1000  avgt    5      166.651 ±     52.215  us/op
MyBenchmark.randomSequential                 1000  avgt    5       28.984 ±      7.477  us/op
MyBenchmark.threadLocalRandomParallel        1000  avgt    5        7.591 ±      0.420  us/op
MyBenchmark.threadLocalRandomSequential      1000  avgt    5        3.178 ±      0.683  us/op

MyBenchmark.randomParallel                  10000  avgt    5     1542.161 ±    710.309  us/op
MyBenchmark.randomSequential                10000  avgt    5      286.011 ±     60.862  us/op
MyBenchmark.threadLocalRandomParallel       10000  avgt    5       18.001 ±      3.008  us/op
MyBenchmark.threadLocalRandomSequential     10000  avgt    5       33.620 ±      4.221  us/op

MyBenchmark.randomParallel               10000000  avgt    5  1511798.753 ± 477838.982  us/op
MyBenchmark.randomSequential             10000000  avgt    5   275049.387 ±  45049.956  us/op
MyBenchmark.threadLocalRandomParallel    10000000  avgt    5    29184.447 ±  34445.026  us/op
MyBenchmark.threadLocalRandomSequential  10000000  avgt    5    37231.631 ±  15880.473  us/op


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

Неожиданным открытием было то, что ThreadLocalRandom работает быстрее Random всегда и везде — на больших, маленьких, параллельных и последовательных операциях. Подозреваю, это из-за синхронизации в Random.

Consider using S.parallelStream().operation(F) instead of S.stream().operation(F) when…

The total time to execute the sequential version exceeds a minimum threshold. These days, the threshold is roughly (within a factor of ten of) 100 microseconds across most platforms.
gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html

А также недавнее упоминание на русском:
— А части ли случаи, когда Stream API проигрывает по перфомансу классическому (Collections) API?

Куксенко: Как правило, Stream API проигрывает по производительности классическому API просто потому что Stream API подразумевает под собой некоторый оверхед, некоторые действия, которые мы обязаны сделать для того чтобы сконструировать хороший стрим и начать с ним работать. Вопрос: что мы выиграем, сделав эти некоторые затраты? Без параллелизации с точки зрения перфоманса тут нечего выигрывать, за исключением более компактного и лучше читаемого кода. Вопрос параллелизации, об этом тоже много говорилось, и писались различные примеры, шаблоны, графики, когда лучше переходить.

Сейчас самая осторожная оценка, которая выше нашей внутренней оценки, и которая, в принципе, достаточно разумна, и ее предлагал Даг Ли: если количество параллелизуемой работы в общем случае превышает 100 миллисекунд, то, параллелизуя ее, можно получить выигрыш с вероятностью 99%. Если количество работы меньше 100 миллисекунд, лучше не заморачиваться параллелизацией. По нашим замерам это 10 миллисекунд, но там уже… Это безопасная граница.
habrahabr.ru/company/jugru/blog/255219

Кстати, 23derevo, выходит что Walrus либо оговорился (100 микросекунд/миллисекунд), либо вы неправильно расшифровали.

Неожиданным открытием было то, что ThreadLocalRandom работает быстрее Random всегда и везде — на больших, маленьких, параллельных и последовательных операциях. Подозреваю, это из-за синхронизации в Random.
Да :)
Walrus либо оговорился (100 микросекунд/миллисекунд), либо вы неправильно расшифровали.

Я спрошу его.
Как раз наоборот, 100 микросекунд это же в 1000 раз меньше, чем 100 миллисекунд. То есть это довольно рискованная, казалось бы, оценка
тьфу, вечер пятницы :)

Спросил Андрея тут.
Скрытый текст
Результат интересный, хотя в принципе логичный. В ThreadLocalRandom сид хранится в поле класса Thread, которое ещё и помечено как Contended. То есть тут самая обычная запись в память, никакой синхронизации, никаких барьеров и хорошая поддержка со стороны кэша процессора. В обычном Random нет синхронизации, но есть AtomicLong, который обновляется через CAS. Даже в одном потоке это дополнительные расходы.

С сегодняшнего дня перехожу на ThreadLocalRandom!
а у тебя так много рандома под контеншеном? ;)

Math.random() компактнее выглядит, глаз не режет. Вот увидит любой из твоих менее шарящих коллег ThreadLocalRandom и заподозрит подвох. Начнет копать не туда… Тебе оно нада?
Math.random() — это вообще плохо. Во-первых, с него можно получить только дробные числа от 0 до 1. Это требуется не очень часто. Чаще нужно Random.nextInt(a, b). А таскать где-то созданный Random на протяжении всего алгоритма может быть не очень удобно. В результате люди либо пишут что-то уродливое вроде (int)(Math.random()*(b-a))+a. В этом случае ThreadLocalRandom.current().nextInt(a, b) гораздо меньше режет глаз.

По хорошему стохастический алгоритм должен принимать на вход фабрику рэндомов. Тогда можно и стабильного поведения для тестов добиться, и тредлокал использовать, если скорость нужна.
Скрытый текст
2 — это только часть ответа. Вызовите getX8 вместо getX9 из обоих методов, и производительность будет одинаковой.
Заголовок спойлера
Не уверен, но думаю, есть какой-то дефолтный лимит на inline. Лишний метод этот лимит нарушает.
Кстати, про regexp — не могу пройти мимо:

Пофиксить можно двумя способами:

Possessive quantifier:

  • Pattern.compile("/\\*(?:[^*]|\\*[^/])*+\\*/")

Independent group:

  • Pattern.compile("/\\*(?>,[^*]|\\*[^/])*\\*/")

Нет, нельзя это фиксать! Я не хочу видеть ни первый, ни второй код в своём приложении. Надо написать грамматику, хоть antlr, хоть на худой конец javacc. Это очень просто, куча примеров в сети, и главное — легко поддерживать и дополнять новыми фичами.

Я неплохо читаю регексы, я даже кроссворд разгадал примерно за час. Но моё, возможно, холиварное мнение в том, что если начинают требоваться штуки с бэктрекингом, то убирайте это и пишите грамматику.
Решение первой задачи
Проблема кода в том, что для большого числа потоков (их количество определяет JDK на основе, видимо, размера массива и прочего) и большого массива может оказаться, что есть contention, т.к. все потоки
будут использовать общий экземпляр класса Random. Там, конечно, не просто synchronize, а CAS вызовы внутри в nextDouble(),
но тем не менее, авторы JDK8 предлагают в таких случаях использовать ThreadLocalRandom.
Просто, чтобы потоки не дрались между собой. Соответственно фикс простой:
    public static double[] getRandomVector(int size) {
        double[] vector = new double[size];
        Arrays.parallelSetAll(vector, i -> ThreadLocalRandom.current().nextDouble());
        return vector;
    }


Sign up to leave a comment.