Вводит ли Java-кастинг накладные расходы? Зачем?



есть ли накладные расходы, когда мы бросаем объекты одного типа в другой? Или компилятор просто решает все и нет никаких затрат во время выполнения?



Это общие вещи, или бывают разные случаи?



например, предположим, что у нас есть массив Object[], где каждый элемент может иметь различные типы. Но мы всегда точно знаем, что, скажем, элемент 0 является двойным, элемент 1-строкой. (Я знаю, что это неправильный дизайн, но давайте просто предположим, что я должен был сделать этот.)



информация о типе Java все еще хранится во время выполнения? Или все забывается после компиляции, и если мы сделаем (двойные)элементы[0], мы просто будем следовать указателю и интерпретировать эти 8 байт как Двойной, что бы это ни было?



Мне очень непонятно, как сделано в Java. Если у вас есть какие-либо рекомендации по книгам или статье, то спасибо тоже.

645   5  

5 ответов:

есть 2 типа литья:

подразумевается литье, когда вы бросаете от типа к более широкому типу, который выполняется автоматически и нет накладных расходов:

String s = "Cast";
Object o = s; // implicit casting

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

Object o = someObject;
String s = (String) o; // explicit casting

в этом втором случае, есть накладные расходы во время выполнения, потому что эти два типа должны быть проверены и в случае, если литья не представляется возможным, виртуальная машина должна бросить ClassCastException.

принято от JavaWorld: стоимость кастинга

литье используется для преобразования между типы-между ссылочными типами в в частности, для типа литья операция, в которой мы заинтересованы здесь.

Upcast операции (также называемые расширение преобразований в Java Спецификация языка) преобразует a производный класс ссылка на предка ссылка на класс. Это кастинг деятельность нормально автоматическая, с тех пор это всегда безопасно и может быть реализуется непосредственно компилятором.

потупив операции (также называемые сужающие преобразования в Java Спецификация языка) преобразует ссылка класса предка на подкласс ссылка. Эта операция литья создает накладные расходы на выполнение, так как Java требует, чтобы приведение было проверено по адресу время выполнения, чтобы убедиться, что это действительно так. Если указанный объект не является экземпляр любого целевого типа для приведение или подкласс этого типа, попытка приведения не допускается и должен бросить Ява.ленг.ClassCastException.

для разумной реализации Java:

каждый объект имеет заголовок, содержащий, среди прочего, указатель на тип среды выполнения (например,Double или String, но он никогда не может быть CharSequence или AbstractList). Предполагая, что компилятор времени выполнения (обычно HotSpot в случае Sun) не может определить тип статически, некоторая проверка должна быть выполнена сгенерированным машинным кодом.

сначала необходимо прочитать указатель на тип среды выполнения. Это необходимо для вызова виртуального метода в подобной ситуации в любом случае.

для приведения к типу класса точно известно, сколько суперклассов есть, пока вы не нажмете java.lang.Object, поэтому тип может быть прочитан с постоянным смещением от указателя типа (фактически первые восемь в HotSpot). Опять же, это аналогично чтению указателя метода для виртуального метода.

тогда значение read просто нуждается в сравнении с ожидаемым статическим типом приведения. В зависимости от архитектура набора инструкций, другая инструкция должна будет ветвиться (или неисправность) на неправильной ветви. ISA, такие как 32-битный ARM, имеют условную инструкцию и могут иметь возможность пропускать грустный путь через счастливый путь.

интерфейсы сложнее из-за множественного наследования интерфейсов. Как правило, последние два приведения к интерфейсам кэшируются в типе среды выполнения. В самые первые дни (более десяти лет назад) интерфейсы были немного медленными, но это уже не так соответствующий.

надеюсь, вы можете видеть, что такого рода вещи в значительной степени не имеют отношения к производительности. Ваш исходный код более важен. С точки зрения производительности, самым большим ударом в вашем сценарии могут быть промахи кэша от преследования указателей объектов по всему месту (информация о типе, конечно, будет общей).

компилятор не отмечает типы отдельных элементов массива. Он просто проверяет, что тип каждого выражения элемента присваивается элементу массива тип.

информация о типе Java все еще хранится во время выполнения? Или все забывается после компиляции, и если мы сделаем (двойные)элементы[0], мы просто будем следовать указателю и интерпретировать эти 8 байт как Двойной, что бы это ни было?

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

теоретически возможно, что JIT-компилятор может использовать "escape-анализ" для устранения ненужных проверок типов в некоторых назначениях. Однако делать это в той степени, в которой вы предлагаете, было бы за пределами реалистичной оптимизации. Отдача от анализа типов отдельных элементов была бы слишком мала.

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

инструкция байтового кода для выполнения приведения во время выполнения называется checkcast. Вы можете разобрать код Java с помощью javap чтобы увидеть, что инструкции генерируются.

для массивов Java сохраняет информацию о типе во время выполнения. Большую часть времени компилятор будет ловить ошибки типа для вас, но есть случаи, когда вы столкнетесь с ArrayStoreException при попытке сохранить объект в массиве, но тип не совпадает (и компилятор не поймал его). Элемент язык Java спецификации приводит следующий пример:

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
    public static void main(String[] args) {
        ColoredPoint[] cpa = new ColoredPoint[10];
        Point[] pa = cpa;
        System.out.println(pa[1] == null);
        try {
            pa[0] = new Point();
        } catch (ArrayStoreException e) {
            System.out.println(e);
        }
    }
}

Point[] pa = cpa действует с ColoredPoint является подклассом точки, но pa[0] = new Point() недопустимо.

это противопоставляется универсальным типам, где нет информации о типе, хранящейся во время выполнения. Компилятор вставляет checkcast инструкции, где это необходимо.

эта разница в типизации для универсальных типов и массивов делает его часто непригодным для смешивания массивов и универсальных типов.

в теории, есть накладные представил. Однако современные СП являются умными. Каждая реализация отличается, но неразумно предполагать, что может существовать реализация, которая JIT оптимизировала проверки кастинга, когда она могла бы гарантировать, что никогда не будет конфликта. Что касается того, какие конкретные СП предлагают это, я не мог вам сказать. Я должен признать, что хотел бы знать специфику JIT-оптимизации сам, но это для инженеров JVM, о которых нужно беспокоиться.

в мораль истории заключается в том, чтобы сначала написать понятный код. Если вы испытываете замедления, профиль и определить вашу проблему. Шансы хорошие, что это не будет связано с кастингом. Никогда не жертвуйте чистым, безопасным кодом в попытке оптимизировать его, пока вы не знаете, что вам нужно.

Comments

    Ничего не найдено.