Избранное сообщение

Фетісов В. С. Комп’ютерні технології в тестуванні. Навчально-методичний посібник. 2-ге видання, перероблене та доповнене / Мои публикации

В 10-х годах я принимал участие в программе Европейского Союза Tempus "Освітні вимірювання, адаптовані до стандартів ЄС". В рамк...

Благодаря Интернету количество писателей и поэтов увеличивается в геометрической прогрессии. Поголовье читателей начинает заметно отставать.

суббота, 11 января 2020 г.

Максимальное количество значений в enum Часть I

Часть первая, теоретическая | Часть вторая, практическая


По мотивам твита от Evgeny Mandrikov aka godin:

Quiz: without running #Java compiler, do you know how many constants can be defined in enum?
— Evgeny Mandrikov (@_godin_) December 13, 2019

В нём он задаётся вопросом, какое максимальное количество значений может быть определено в перечислении (enum) в Java. После ряда экспериментов и применения чёрной магии ConstantDynamic (JEP 309) автор вопроса приходит к числу 8191.

В серии из двух статей поищем теоретические пределы числа элементов в перечислении, попробуем к ним приблизиться на практике и попутно выясним, чем может помочь JEP 309.

Рекогносцировка

Обзорная глава, в которой мы впервые видим перечисление дизассемблированным.

Для начала посмотрим, во что транслируется следующее перечисление:

public enum FizzBuzz {

  Fizz,

  Buzz,

  FizzBuzz;

}


После компиляции и дизассемблирования:

javap -c -s -p -v FizzBuzz.class


В листинге нас встречают

— По одному public static final полю для каждого значения, определённого в перечислении

— Приватное синтетическое поле $VALUES, деталь реализации метода values()

— Реализация методов values() и valueOf()

— Приватный конструктор

— Блок статической инициализации, где собственно и происходит всё самое интересное. Рассмотрим его подробнее.

В виде java-кода последний выглядит примерно так:

    static  {
        Fizz = new FizzBuzz("Fizz", 0);
        Buzz = new FizzBuzz("Buzz", 1);
        FizzBuzz = new FizzBuzz("FizzBuzz", 2);

        $VALUES = new FizzBuzz[] {
            Fizz, 
            Buzz, 
            FizzBuzz
        };
    }


Вначале создаются экземпляры элементов перечисления. Созданные экземпляры немедленно записываются в соответствующие public static final поля.

Затем создаётся и заполняется массив со ссылками на экземпляры всех элементы перечисления. Ссылки достаются из полей класса, которые мы инициализировали абзацем выше. Заполненный массив сохраняется в private static final поле $VALUES.

После этого перечисление готово к работе.

Бутылочное горлышко


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

Начать поиски можно с главы JLS §8.9.3 «Enum Members»:



Итак, у каждого класса-перечисления есть метод values(), который возвращает массив со всеми объявленными в данном перечислении элементами. Из этого следует, что сферическое перечисление в вакууме не может содержать более Integer.MAX_VALUE + 1 элементов.

Движемся дальше. Перечисления в Java представляются в виде наследников класса java.lang.Enum, а следовательно на них распространяются все ограничения, присущие классам в JVM.

Посмотрим на высокоуровневое описание структуры class-файла, приведённое в JVMS §4.1 «The ClassFile Structure»:

ClassFile {
 u4 magic;
 u2 minor_version;
 u2 major_version;
 u2 constant_pool_count;
 cp_info constant_pool[constant_pool_count-1];
 u2 access_flags;
 u2 this_class;
 u2 super_class;
 u2 interfaces_count;
 u2 interfaces[interfaces_count];
 u2 fields_count;
 field_info fields[fields_count];
 u2 methods_count;
 method_info methods[methods_count];
 u2 attributes_count;
 attribute_info attributes[attributes_count];
}


Как мы уже знаем из JLS §8.9.3, для каждого элемента перечисления в результирующем классе создаётся одноимённое поле. Число полей в классе задаёт 16-битное беззнаковое fields_count, что ограничивает нас 65_535 полями в одном class-файле или 65_534 элементами перечисления. Одно поле зарезервировано под массив $VALUES, клон которого возвращает метод values(). Это не прописано явно в спецификации, но вряд ли получится придумать более изящное решение.

Имена полей, методов, классов, константные значения и многое другое хранится в пуле констант.

Если вы совсем ничего не знаете про внутреннее устройство пула констант, рекомендую почитать древнюю статью от lany. Не смотря на то, что с момента её написания в пуле констант появилось много нового и интересного, основные принципы остаются неизменными.


Размер пула констант класса ограничен также числом в 65_535 элементов. Пул констант корректно сформированного класса никогда не бывает пуст. Как минимум, там будет имя этого класса.

К примеру, пул констант класса пустого перечисления, скомпилированный javac из OpenJDK 14-ea+29 без отладочной информации содержит 29 вхождений.

Из этого следует, что число в 65_534 элемента в одном перечислении также недостижимо. В лучшем случае можем рассчитывать на 65_505 или близкое к этому число.

Последний аккод в этом затянувшемся вступлении:

Записать значение в static final поле можно только в блоке статической инициализации, который на уровне class-файла представлен методом с именем <clinit>. Байткод любого метода при этом не может занимать более 65_535 байтов. Знакомое число, не правда ли?

Одна инструкция записи в статическое поле putstatic занимает 3 байта, что даёт нам грубую оценку в 65_535 / 3 = 21_845. На самом деле эта оценка завышена. Значение для записи в поле инструкция берёт с вершины стека, которое туда поместила одна из предыдущих инструкций. И эта инструкция тоже занимает драгоценные байты. Но даже если не принимать это во внимание, полученное число всё равно значительно меньше 65_505.

Резюмируя:
— Формат class-файла ограничивает максимальное число элементов перечисления примерно 65_505
— Механизм инициализации static final полей ограничивает нас ещё сильнее. Теоретически — до 21_845 элементов максимум, на практике это число ещё меньше

В заключительной статье цикла займёмся нездоровой оптимизацией и генерацией class-файлов.

Источник: https://habr.com/ru/post/483392/?utm_source=habrahabr&utm_medium=rss&utm_campaign=483392

Смотри также:

Функциональное программирование: в Java и C# слишком много церемоний. http://fetisovvs.blogspot.com/2017/05/java-c.html

Комментариев нет:

Отправить комментарий