Почему у Java есть примитивы для чисел разных размеров?

20

В Java есть примитивные типы для byte, short, intи longи то же самое для floatи double. Почему необходимо, чтобы человек установил, сколько байтов должно использоваться для примитивного значения? Разве размер не может быть определен динамически в зависимости от того, насколько большим было переданное число?

Есть две причины, по которым я могу думать:

  1. Динамическая установка размера данных будет означать, что также потребуется возможность динамического изменения. Это может привести к проблемам с производительностью?
  2. Возможно, программист не хотел бы, чтобы кто-то мог использовать большее число, чем определенный размер, и это позволяет им ограничивать его.

Я все еще думаю, что можно было бы многого выиграть, просто используя single intи floattype. Была ли конкретная причина, по которой Java решила не идти по этому пути?

yitzih
источник
4
Я бы добавил, что этот вопрос связан с вопросом, на который исследователи компилятора стремятся ответить .
Руонг
Таким образом, если вы добавили к числу, вы думаете, что тип должен быть динамически изменен? Я даже хочу, чтобы тип изменился? Если число инициализируется как intUnknown alpha = a + b; Вы понимаете, что это будет немного сложно для компилятора. Почему это специфично для Java?
Папараццо
@Paparazzi Существуют существующие языки программирования и среды исполнения (компиляторы, интерпретаторы и т. Д.), В которых будет храниться целое число динамической ширины в зависимости от того, насколько велико фактическое значение (например, результат операции сложения). Последствия таковы: код, выполняемый на ЦП, становится более сложным; размер этого целого становится динамическим; чтение целого числа из динамической ширины из памяти может потребовать более одного отключения; структуры (объекты) и массивы, которые содержат целые числа динамической ширины внутри своих полей / элементов, также могут иметь динамический размер.
Rwong
1
@ Тофро, я не понимаю. Просто отправьте число в любом формате: десятичный, двоичный и т. Д. Сериализация - это абсолютно ортогональная задача.
садовод
1
@gardenhead Это ортогонально, да, но ... просто рассмотрим случай, когда вы хотите установить связь между сервером, написанным на Java, и клиентом, написанным на C. Конечно, это можно решить с помощью выделенной инфраструктуры. Например, есть такие вещи, как developers.google.com/protocol-buffers . Но это большой кувалдой для маленького ореха передачи целого числа по сети. (Я знаю, это не сильный аргумент здесь, но, возможно, стоит подумать - обсуждение деталей выходит за рамки комментариев).
Marco13

Ответы:

16

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

альтернативы

Конечно, возможно (и довольно просто) создать язык программирования, который имеет только один тип натуральных чисел nat. Почти все языки программирования, используемые для академического обучения (например, PCF, System F), имеют этот тип единого числа, который, как вы и предполагали, является более элегантным решением. Но языковой дизайн на практике - это не просто элегантность; мы также должны учитывать производительность (степень, в которой рассматривается производительность, зависит от предполагаемого применения языка). Производительность включает в себя как временные, так и пространственные ограничения.

Пространство

Разрешение программисту выбрать количество байтов заранее может сэкономить место в программах с ограниченным объемом памяти. Если все ваши номера будут меньше , чем 256, то вы можете использовать 8 раз больше , чем byteс , как longс, или использовать сохраненную память для более сложных объектов. Разработчику стандартных Java-приложений не нужно беспокоиться об этих ограничениях, но они все же возникают.

КПД

Даже если мы игнорируем пространство, мы все еще ограничены процессором, который имеет только инструкции, которые работают с фиксированным числом байтов (8 байтов в 64-битной архитектуре). Это означает, что даже предоставление одного 8-байтового longтипа значительно упростит реализацию языка по сравнению с неограниченным типом натуральных чисел, поскольку можно будет отображать арифметические операции непосредственно в отдельные базовые инструкции ЦП. Если вы позволяете программисту использовать произвольно большие числа, то одна арифметическая операция должна быть сопоставлена ​​с последовательностью сложных машинных инструкций, что приведет к замедлению работы программы. Это пункт (1), который вы подняли.

Типы с плавающей точкой

Обсуждение до сих пор касалось только целых чисел. Типы с плавающей точкой - сложный зверь с чрезвычайно тонкой семантикой и крайними случаями. Таким образом, даже если мы могли бы легко заменить int, long, shortи byteс помощью одного natтипа, то не ясно , что тип чисел с плавающей точкой , даже есть . Очевидно, они не являются действительными числами, поскольку действительные числа не могут существовать в языке программирования. Они также не совсем рациональные числа (хотя при желании можно создать рациональный тип). По сути, IEEE выбрал способ сортировки приближенных действительных чисел, и с тех пор все языки (и программисты) были привязаны к ним.

В заключение:

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

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

gardenhead
источник
2
реальный ключ к тому, что у нас есть поплавки, это то, что у нас есть специальное оборудование для них
jk.
Кроме того, кодирование числовых границ в типе действительно происходит в языках зависимого типа и в меньшей степени в других языках, например, в enums
jk.
3
Перечисления не эквивалентны целым числам. Перечисления - это просто способ использования типов сумм. Тот факт, что некоторые языки прозрачно кодируют перечисления как целые числа, является недостатком языка, а не возможностью использования.
садовод
1
Я не знаком с Ада. Могу ли я ограничить целые числа для любого типа, например type my_type = int (7, 2343)?
садовник
1
Ага. Синтаксис будет следующим: type my_type находится в диапазоне 7..2343
Devsman
9

Причина очень проста: эффективность . Несколькими способами.

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

  2. Неоправданные накладные расходы в 32-разрядных системах: если бы было принято решение отобразить все на 64-разрядную длину фиксированного размера, это привело бы к огромным потерям для 32-разрядных архитектур, которым для выполнения 64-разрядных операций требуется значительно больше тактовых циклов. битовая операция, чем 32-битная операция.

  3. Трата памяти: существует большое количество оборудования, которое не слишком требовательно к выравниванию памяти (например, архитектуры Intel x86 и x64), поэтому массив из 100 байтов на этом оборудовании может занимать только 100 байтов памяти. Однако, если у вас больше нет байта, и вместо этого вам нужно использовать длинный, этот же массив займет на порядок больше памяти. И байтовые массивы очень распространены.

  4. Вычисление размеров чисел. Ваше представление о динамическом определении размера целого числа в зависимости от того, насколько большим было переданное число, слишком упрощено; нет единой точки «ввода» числа; вычисление того, насколько большим должно быть число, должно выполняться во время выполнения для каждой отдельной операции, для которой может потребоваться результат большего размера: каждый раз, когда вы увеличиваете число, каждый раз, когда вы добавляете два числа, каждый раз, когда вы умножаете два номера и т. д.

  5. Операции над числами разных размеров. Следовательно, наличие в памяти чисел потенциально разных размеров усложнит все операции: даже для простого сравнения двух чисел среда выполнения сначала должна проверить, совпадают ли оба сравниваемых числа. размер, и если нет, измените размер меньшего, чтобы соответствовать размеру большего.

  6. Операции, для которых требуются определенные размеры операндов. Некоторые побитовые операции основаны на целочисленных значениях определенного размера. Не имея заранее определенного конкретного размера, эти операции придется эмулировать.

  7. Накладные расходы на полиморфизм: изменение размера числа во время выполнения по существу означает, что оно должно быть полиморфным. Это, в свою очередь, означает, что это не может быть примитив фиксированного размера, выделенный в стеке, это должен быть объект, выделенный в куче. Это ужасно неэффективно. (Перечитайте №1 выше.)

Майк Накис
источник
6

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

С точки зрения языкового дизайна

  • Безусловно, можно спроектировать и реализовать язык программирования и его среду выполнения, которая будет автоматически учитывать результаты целочисленных операций, которые не соответствуют ширине машины.
  • Разработчик языка выбирает, делать ли такие целые числа динамической ширины целочисленным типом по умолчанию для этого языка.
  • Тем не менее, разработчик языка должен учитывать следующие недостатки:
    • Процессору придется выполнять больше кода, что занимает больше времени. Тем не менее, можно оптимизировать для наиболее частого случая, когда целое число соответствует одному машинному слову. Смотрите теговое представление указателя .
    • Размер этого целого становится динамическим.
    • Чтение целого числа из динамической ширины из памяти может потребовать более одного отключения.
    • Структуры (объекты) и массивы, которые содержат динамические целые числа ширины внутри своих полей / элементов, будут иметь общий (занятый) размер, который также является динамическим.

Исторические причины

Это уже обсуждалось в статье в Википедии об истории Java, а также кратко обсуждается в ответе Marco13 .

Я хотел бы отметить, что:

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

Причины эффективности

Когда важна эффективность?

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

Эффективность хранения (в памяти или на диске)

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

Эффективность исполнения (внутри процессора или между процессором и памятью)

  • Уже обсуждалось в ответе Gardenhead .
  • Если программе необходимо обрабатывать очень большие массивы небольших чисел, хранящихся последовательно, эффективность представления в памяти напрямую влияет на производительность ее выполнения, поскольку большое количество данных приводит к тому, что пропускная способность между процессором и памятью становится узким местом. В этом случае более плотная упаковка данных означает, что одна выборка строки кэша может извлечь больше фрагментов данных.
  • Однако это рассуждение не применяется, если данные не сохраняются или не обрабатываются последовательно.

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

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

Interoperability

  • Зачастую языки программирования более высокого уровня должны взаимодействовать с операционной системой или программными компонентами (библиотеками), написанными на других языках более низкого уровня. Эти языки более низкого уровня часто общаются, используя «структуры» , что является жесткой спецификацией структуры памяти записи, состоящей из полей разных типов.
  • Например, языку более высокого уровня может потребоваться указать, что определенная сторонняя функция принимает charмассив размером 256. (Пример.)
  • Некоторые абстракции, используемые операционными системами и файловыми системами, требуют использования байтовых потоков.
  • Некоторые языки программирования предпочитают предоставлять служебные функции (например BitConverter), которые помогают упаковывать и распаковывать узкие целые числа в битовые потоки и байтовые потоки.
  • В этих случаях узкие целочисленные типы не обязательно должны быть примитивными типами, встроенными в язык. Вместо этого они могут быть предоставлены как тип библиотеки.

Обработка строк

  • Существуют приложения, основной целью которых является манипулирование строками. Таким образом, эффективность обработки строк важна для этих типов приложений.

Обработка формата файла

  • Многие форматы файлов были разработаны с использованием мышления типа C. Таким образом, использование полей узкой ширины было распространено.

Желательность, качество программного обеспечения и ответственность программиста

  • Для многих типов приложений автоматическое расширение целых чисел фактически нежелательно. Ни насыщение, ни обтекание (модуль).
  • Многие типы приложений выиграют от явной спецификации программистом максимально допустимых значений в различных критических точках программного обеспечения, например на уровне API.

Рассмотрим следующий сценарий.

  • Программный API принимает запрос JSON. Запрос содержит массив дочерних запросов. Весь JSON-запрос может быть сжат с помощью алгоритма Deflate.
  • Вредоносный пользователь создает запрос JSON, содержащий один миллиард дочерних запросов. Все дочерние запросы идентичны; злонамеренный пользователь намерен заставить систему сжечь несколько циклов ЦП, выполняя бесполезную работу. Из-за сжатия эти идентичные дочерние запросы сжимаются до очень маленького общего размера.
  • Очевидно, что предопределенного ограничения на сжатый размер данных недостаточно. Вместо этого API должен наложить предопределенное ограничение на число дочерних запросов, которые могут содержаться в нем, и / или предопределенное ограничение на дефлированный размер данных.

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

Это означает точку зрения ОП,

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

не является правильным. Программист должен иметь возможность, а иногда и необходимость, указывать максимальную величину, которую может принимать целочисленное значение в критических частях программного обеспечения. Как указывает ответ садовника , естественные ограничения, налагаемые примитивными типами, бесполезны для этой цели; язык должен предоставлять программистам возможность объявлять величины и применять такие ограничения.

rwong
источник
2

Все это происходит от оборудования.

Байт - это самая маленькая адресуемая единица памяти на большинстве аппаратных средств.

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

Байт 8 бит. При этом вы можете выразить 8 логических значений, но вы не можете искать только по одному за раз. Вы обращаетесь к 1, вы обращаетесь ко всем 8.

Раньше все было так просто, но потом мы перешли с 8-битной шины на 16, 32, а теперь и 64-битную шину.

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

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

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

Java на самом деле пытается это сделать. Байты автоматически повышаются до Ints. Факт, который смутил вас с первого раза, когда вы попытаетесь выполнить в нем какую-то серьезную работу.

Так почему же это не сработало?

Явная большая распродажа Java в то время, когда вы могли бы использовать известный хороший алгоритм C, набрать его в Java, и с небольшими изменениями это сработало бы. И С очень близок к оборудованию.

Сохранение этого идущего и абстрагирование размера от целочисленных типов просто не работало вместе.

Чтобы они могли иметь. Они просто не

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

Это верное мышление. Есть способы сделать это. Функция зажима для одного. Язык может зайти так далеко, чтобы запечь произвольные границы для их типов. И когда эти границы известны во время компиляции, это позволит оптимизировать хранение этих чисел.

Ява просто не тот язык.

candied_orange
источник
« Язык может зайти так далеко, чтобы запекать произвольные границы для их типов ». И действительно, у Паскаля есть такая форма с поддиапазонными типами.
Питер Тейлор
1

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

C и C ++ также имели эти типы!

Хотя трудно доказать, что это является причиной, есть хотя бы некоторые веские доказательства: спецификация языка дуба (версия 0.2) содержит следующий отрывок:

3.1 Целочисленные типы

Целые числа в языке Oak похожи на целые числа в C и C ++, с двумя исключениями: все целочисленные типы являются машинно-независимыми, и некоторые традиционные определения были изменены, чтобы отразить изменения в мире с момента появления C. Четыре целочисленных типа имеют ширину 8, 16, 32 и 64 бита и имеют подпись, если они не префиксированы unsignedмодификатором.

Таким образом, вопрос может сводиться к:

Почему были короткие, инт, и длинные изобрели в C?

Я не уверен, является ли ответ на вопрос о письме удовлетворительным в контексте вопроса, который был задан здесь. Но в сочетании с другими ответами здесь может стать ясно, что эти типы могут быть полезны (независимо от того, является ли их существование в Java только наследием от C / C ++).

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

  • Байт - это наименьший адресуемый блок памяти (как уже упоминалось в CandiedOrange). A byte- это элементарный строительный блок данных, который можно прочитать из файла или по сети. Должно существовать некоторое явное представление об этом (и оно существует в большинстве языков, даже если иногда оно скрыто).

  • Это правда, что на практике имеет смысл представлять все поля и локальные переменные, используя один тип, и вызывать этот тип int. С этим связан стековый поток: почему Java API использует int вместо short или byte? , Как я уже упоминал в своем ответе, одно из оправданий наличия меньших типов ( byteи short) состоит в том, что вы можете создавать массивы этих типов: Java имеет представление массивов, которое все еще довольно "близко к аппаратному обеспечению". В отличие от других языков (и в отличие от массивов объектов, таких как Integer[n]массив), int[n]массив не является коллекцией ссылок, значения которых разбросаны по всей куче. Вместо этого это будетна практике это последовательный блок n*4байтов - один кусок памяти с известным размером и разметкой данных. Если у вас есть выбор сохранения 1000 байтов в коллекции объектов целочисленных значений произвольного размера или в byte[1000](который занимает 1000 байтов), последний действительно может сэкономить некоторую память. (Некоторые другие преимущества этого могут быть более тонкими и становятся очевидными только при взаимодействии Java с нативными библиотеками)


Что касается вопросов, о которых вы конкретно спрашивали:

Разве размер не может быть определен динамически в зависимости от того, насколько большим было переданное число?

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

Вероятно, было бы возможно динамически установить размер переменных, если задуматься о разработке совершенно нового языка программирования с нуля. Я не эксперт в построении компиляторов, но думаю, что было бы трудно разумно управлять коллекциями динамически изменяемых типов - особенно когда у вас строго типизированный язык. Таким образом, это, вероятно, сводится к тому, что все числа хранятся в «универсальном типе данных с произвольной точностью», что, безусловно, будет влиять на производительность. Конечно, есть языки программирования, которые строго типизированы и / или предлагают числовые типы произвольного размера, но я не думаю, что существует настоящий язык программирования общего назначения, который пошел бы по этому пути.


Примечания стороны:

  • Возможно, вы задались вопросом о unsignedмодификаторе, который был упомянут в спецификации Oak. На самом деле, он также содержит замечание: « unsignedеще не реализовано; возможно, никогда не будет». , И они были правы.

  • В дополнение к удивлению, почему в C / C ++ вообще есть эти разные целочисленные типы, вы можете удивиться, почему они испортили их так ужасно, что вы никогда не знаете, сколько бит intимеет. Обоснования этого обычно связаны с производительностью и могут быть найдены в другом месте.

Marco13
источник
0

Это, безусловно, показывает, что вы еще не учили о производительности и архитектуре.

  • Во-первых, не каждый процессор может обрабатывать большие типы, поэтому вам нужно знать ограничения и работать с этим.
  • Во-вторых, меньшие типы означают большую производительность при выполнении операций.
  • Кроме того, размер имеет значение, если вам нужно хранить данные в файле или базе данных, размер повлияет как на производительность, так и на конечный размер всех данных, например, скажем, у вас есть таблица с 15 столбцами, и в итоге вы получите несколько миллионы записей. Разница между выбранным небольшим необходимым размером для каждого столбца или выбранным самым большим типом будет разницей в возможных гигабайтах данных и времени при выполнении операций.
  • Кроме того, он применяется в сложных вычислениях, где размер обрабатываемых данных будет иметь большое влияние, как, например, в играх.

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

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

Нестор Мата Катберт
источник
0

Есть несколько веских причин

(1) в то время как хранение однобайтовых переменных в одной длинной незначительно, хранение миллионов в массиве очень важно.

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

ddyer
источник