КаталогИндекс раздела
НазадОглавлениеВперед


Языки программирования и их реализация

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

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

либо "A; B" либо " B; A",

а именно, за действием "A" следует во времени действие "B" либо наоборот. Он не будет выражать, что порядок является несущественным и, что, возможно, более важно, он не будет выражать, что два действия являются настолько "неперекрывающимися" друг с другом, так что они могут выполняться одновременно или - используя жаргон - могут выполняться параллельно.

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

С наступлением того, что мы называем "крупномасштабной интеграцией" (в терминах компьютерной области лучше известна аббревиатура LSI [large scale integration]!) представляется, что становится возможным строить машины, являющиеся чем-то большим, чем "кучей арифметических устройств", с процессами обработки информации, проходящими одновременно во всех местах, в короткие периоды времени даже не завися друг от друга. Программирование для таких машин будет иметь совсем другие проблемы компромиссов: нужно быть в состоя выполнить потенциально нужное вычислительное действие до того, как установится его действительная необходимость, в целях ускорения всего вычисления. Но хотя я знаю, что такие машины могут появиться, я не буду затрагивать эти проблемы по следующим причинам. Во-первых, пока рассматриваются приложения общего назначения, у меня есть сомнения по поводу эффективности использования такой формы параллелизма. Во-вторых - и это более важное соображение, - параллельное программирование на порядок более сложно, чем последовательное программирование. (Это утверждение может быть поставлено под сомнение, но я имею достаточный опыт в мультипрограммировании, чтобы чувствовать себя вправе так говорить. Проблема в том, что при параллелизме большое число явлений могут происходить под управлением одной и той же программы (одних и тех же программ). У учетом неопределенного соотношения скоростей ряд параллельных программ пишется для частично недетерминированной машины и требуется специальное рассмотрение для того, чтобы гарантировать, что на более высоком уровне абстрагирования их общее поведение может вновь рассматриваться как однозначно детерминированной программой (программами).) В-третьих, меня не очень убеждают жалобы на то, что последовательные программы определяют более строгую временную последовательность, чем это логически необходимо: у меня часто возникает тревожное ощущение, что эти жалобы показывают, что их источники мыслят в математических традициях до-компьютерного века. В классической математике нотацией алгоритма пренебрегали, заметьте, я не осуждаю ранних математиков за это, поскольку до реального существования автоматических компьютеров алгоритмы вряд ли были важной темой. Но мы не должны закрывать глаза на тот факт, что ход истории заставляет математику больше ориентироваться на вечные проблемы, на статические соотношения, на функциональную зависимость. (Существование классической механики не противоречит этому наблюдению: переименование в дифференциальном уравнении независимой переменной в "k" вместо обычного "t" не повлияет на вовлеченную математику.) Некоторые попытки устранения слишком строгого определения временной последовательности - они надеются на функциональную зависимость - представляются мне как соединение проблем программирования с классическими концепциями, которые были разработаны для других целей. Вот все по поводу моего решения ограничить мое рассмотрение последовательными машинами.

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

a)  в нее вовлечено большое количество чисел: постановка задачи подразумевает определение 10.000 чисел, ответ также задается 10.000 чисел (каждое из которых в общем случае является функцией всех 10.000 элементов заданной матрицы);
b)  в нее вовлечено большое количество вычислений: количество операций (т.е., умножений и сложений) - порядка 1.000.000.

Конструкция машины, позволяющей справиться (наверняка!) с этими двумя очень трудными аспектами "множественности", является одним из величайших триумфов электроники. Он был достигнут применением старого и хорошо известного принципа: "Разделяй и властвуй". В современных компьютерах можно выделить два жизненно важных компонента, каждый из которых имеет специфическую задачу справляться с одной из форм множественности.
a)  хранилище (store) (в Америке его называют "памятью" (memory)); это компонент, способный принимать, хранить и возвращать огромные количества информации; его первостепенной функцией является быть большим, чтобы быть в состоянии содержать очень много информации [В русскоязычной терминологии закрепилась калька с американского "память", которую мы далее и применяем для удобства читателя, на в оригинале везде применяется "хранилище", поскольку ЭВД был противником переноса антропоморфной терминологии в информатику. - прим. перев.];
b)  арифметическое устройство или процессор; это компонент, на котором на самом деле выполняется работа - сложение, вычитание, умножение, сравнение и т.д.; его первостепенной функцией является быть очень быстрым, так как он должен выполнить громадную работу в ограниченный период времени.

Быть большим, в смысле содержать большие количества информации не является функцией арифметического устройства. И наоборот: пока почти вся информация, в полном объеме относящаяся к вычислению, лежит и "спит" в памяти, в любой момент времени только маленькая часть, действительно участвующая в действие по обработке информации, находится (скопирована) в арифметическом устройстве, которое способно работать только с несколькими числами за раз, скажем, с двумя числами, которые нужно сложить и суммой, формируемой действием сложения. Когда нужно сложить два числа (в памяти), они передаются из памяти на арифметическое устройство, где формируется сумма; сформированная сумма либо сохраняется на арифметическом устройстве для дальнейшей немедленной обработки, либо посылается назад в память для обработки впоследствии. На микроуровне память работает как холодильник, в котором сберегается вся информация, которая не вовлечена в текущую деятельность арифметического устройства. Если обозначить маленькими буквами переменные в памяти, а через R обозначить регистр в арифметическом устройстве, то вычисление

x:= (a+b)*(c + d) 

может быть выполнено следующей последовательностью инструкций

R:= a;
R:= R + b; 
t:= R; 
R:= c; 
R:=R + d;
R:= t * R;
x:= R

Первая инструкция выбирает значение "a" из памяти в регистр R, следующая увеличивает (содержимое) R на значение "b" (из памяти). На этом этапе один из двух сомножителей, которые должны быть перемножены, вычислен. Прежде, чем сможет выполниться умножение, должен быть вычислен также и второй сомножитель; в машине с одним регистром R для арифметического результата это второе сложение предполагает также использование регистра R. Чтобы сделать этот регистр доступным для этой цели, третья инструкция посылает значение первого сомножителя - так называемый, "промежуточный результат" - назад в память, присваивая его переменной, названной "t": первая сума послана назад в память для использования впоследствии. Четвертая и пятая инструкции вычисляют следующий сомножитель, значение которого остается в регистре R, готовое к умножению на сохраненное значение с именем "t". Последняя инструкция сохраняет результат, сейчас сформированный в R, так что он может быть выбран по имени "x" для использования впоследствии.

Приведенный выше пример иллюстрирует многое. Он показывает, как

x:= (a+b)*(c + d) 

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

x:= (a+b)/(c + d) 

единственной необходимой модификацией должна быть замена шестой инструкции на

R:= t / R; 

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

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

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

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

R:= t * R   , 

память не должна возвращать какое попало число, но совершенно определенно она должна возвращать значение, посланное в нее двумя инструкциями ранее. По этой причине числа в памяти упорядочены не как шары в урне, наоборот! Память организована как множество, так называемых, "ячеек памяти", каждая из них способна хранить значение одного числа за раз. Каждая ячейка памяти идентифицируется, так называемым, "адресом"; всякий раз, когда требуется контакт с памятью - либо для посылки, либо для возврата информации - этот запрос сопровождается адресом соответствующей ячейки памяти. Если в память посылается информация - это называется "записью в память" - значение, которое записывается, и адрес соответствующего места в памяти (плюс "запрос на запись") посылаются в память и в механизм выборки соответственно; в результате операции записи исходное содержимое ячейки памяти теряется и заменяется на новое значение. Если память должна вернуть информацию - это называется "чтением из памяти" - адрес ячейки памяти (плюс "запрос на чтение") посылается в механизм выборки; в результате операции чтения содержимое ячейки памяти возвращается из памяти (и сохраняется в ячейке памяти для чтения впоследствии, если потребуется). Когда мы рассматриваем уничтожение, получение и воспроизведение информации, содержащейся в ячейке памяти, мы обнаруживаем большое сходство ситуации с лентой в магнитофоне. Вы можете использовать ленту, чтобы записать столько музыкальных произведений, сколько захотите, но только по одному за раз: если вы записываете новое музыкальное произведение на старую ленту, ее предыдущее содержимое стирается; записанное же сейчас музыкальное произведение может быть проиграно столько раз, сколько вы хотите. (Чтобы сделать аналогию полной мы должны ограничить себя музыкальными произведениями одинаковой длительности, точно соответствующей длине ленты в соответствии с ее (конечной) информационной емкостью.)

Ячейки памяти могут хранить информацию в силу того факта, что они что они могут находиться в конечном числе различных состояний. Практически во всех компьютерах они состоят их элементарных компонентов, каждый из которых может быть в одном из двух возможных состояний. (Наиболее распространенной формой является маленькое кольцо из ферромагнитного материала, которое может быть циркулярно намагничено в одном из двух направлений.) Один такой компонент может быть в 2 разных состояниях (скажем, "Север" и "Юг"), два таких компонента вместе могут быть в 4 разных общих состояниях ("Север-Север"б "Север-Юг", "Юг-Север" и "Юг-Юг"), N таких компонентов вместе могут быть в 2N разных состояниях. Число элементарных компонентов, связанных с каждой ячейкой памяти, является константой, характеризующей память машины и называется "длиной слова". Если длина слова 32, число различных возможных состояний слова составляет 232, т.е, несколько больше 4*109; арифметическое устройство будет ассоциировать с каждым состоянием числовое значение; в терминах этих числовых значений ячейка памяти может содержать целое число в пределах (округленно) от -2*109 до +2*109.

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

Теперь мы достаточно объяснили общую структуру машины, чтобы понять два аспекта "стоимости", вовлеченных в выполнение программы. Один из них - время вычисления. Вы видели, что арифметическое устройство выполняет операции одну за другой, и чем больше операций предписывается программой, тем большее бремя должно потратить арифметическое устройство, чтобы довести программу до завершения. Другой - использование памяти. Если нам нужно вычислить тысячу значений, чтобы потом их сложить, мы можем сравнить следующие два алгоритма. Первый сначала вычисляет всю тысячу значений и сохраняет их, после чего они складываются; второй алгоритм немедленно прибавляет каждое число к частичной сумме, как только они вычислено. В отношении использования памяти первый алгоритм более требовательный: на некотором этапе вычислений он требует объема памяти, достаточного для сохранения тысячи значений, значительное количество памяти во втором алгоритме остается доступным для других (возможно, более полезных) целей.

Также влияет на конечность емкости памяти и тот факт, что память содержит только конечное число таких ячеек. Давайте вернемся к их адресам: недавно мы дали понять, что каждая ячейка памяти идентифицируется ее "адресом" и что каждое обращение к памяти происходит под управлением адреса соответствующей ячейки памяти, но до сих пор мы не имели полной ясности в том, что же представляет из себя адрес на самом деле. Ну, это очень просто: ячейки памяти пронумерованы: 0, 1, 2, 3, 4, 5, ... до M-1, если память включает в себя M ячеек (типичным является M между 16.000 и 1.000.000 [1971 год! - прим. перев.]), и обычный номер каждой ячейки памяти используется как ее адрес (как дома на улице!). Это предполагает, что ячейки памяти имеют естественный порядок, а именно, порядок увеличения адресов. Если имеется адрес ячейки памяти, адрес следующей ячейки может быть вычислен прибавлением 1 к имеющемуся адресу предыдущей.

Этот естественный порядок ячеек памяти интенсивно используется. Если должен быть сохранен вектор, т.е., последовательность чисел a0, a1, ... , an, его элементы могут быть сохранены в последовательных ячейках памяти. Если адрес элемента a0 известен, то адрес элемента ai может быть вычислен, а именно, прибавлением (значения) i к адресу элемента a0.

Естественный порядок ячеек памяти также используется в представлении программ. Вспомните, что мы постулировали, что машина может "принимать" программу и что, если программа принята, машина может выполнять программу (т.е., вызывать явление, которое предписано в программе) Другими словами, когда машина выполняет программу, эта программа, т.е., "информация, описывающая, как себя вести", должна быть где-то в машине! Где? Правильно, в памяти, память является специальным компонентом машины для хранения информации. Другими словами, память используется для двух разных целей: она хранит числовую информацию для обработки, но также она хранит - в некоторой другой своей части - программу, т.е., информацию, описывающую, какая обработка должна быть выполнена.

Чтобы кратко описать, как это может быть сделано, мы вернемся к нашему предыдущему примеру, где

x:= (a+b)*(c + d) 

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

R:= a;
R:= R + b; 
t:= R; 
R:= c; 
R:=R + d;
R:= t * R;
x:= R

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

Первое соглашение состоит в выборе для каждой инструкции уникального числового кода. В приведенной выше нотации мы обозначили переменные (или: адреса ячеек памяти, связанные с переменными и содержащие их текущие значения) маленькими буквами (a, b, c, d, t и x); но адреса являются числами, и эти компоненты, следовательно, уже числовые. Используя "s" как "любой адрес", мы видим, что в вышеприведенном примере мы можем различить инструкции четырех разных типов:

1)          R:= s
2) R:= R + s
3) R:= s * R
4) s:= R

Вторая часть соглашения связывает с каждым типом инструкции номер (напр., номера 1, 2, 3 и 4 для показанных выше типов; следует иметь в виду, что в настоящих машинах число типов инструкций значительно больше 4). Конкатенацией цифр, описывающих номер типа инструкции, с цифрами заданного адреса мы получаем числовой код для каждой возможной инструкции, и мы предполагаем, что длина слова ячейки памяти достаточна, чтобы вместить такое число. Первое соглашение, как сейчас описано, сводит проблему хранения последовательности инструкций к хранению последовательности чисел. Теперь второе соглашение состоит в том, что такая последовательность представляется сохранением этих чисел в последовательных ячейках памяти, т.е., в ячейках памяти с последовательными адресами. И этим трюк завершается (приблизительно).

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

Двойная роль памяти - хранилище инструкций и хранилище переменных - предполагает и другую причину, по которой программа может быть дорогой для выполнения: если текст программы очень большой, то он будет предъявлять большие требования к емкости памяти. Если у нас есть две альтернативные программы для одной и той же задачи, одна требует 5000 инструкций для ее описания, а другая требует для этого 10000 инструкций, то - при прочих равных условиях - первая альтернатива будет дешевле.

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

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

Но даже и без проблемы переноса программ с одного оборудования на другое этот путь программирования, выражение программ непрерывным потоком машинных инструкций, демонстрирует много недостатков. Один серьезный недостаток был том, что этот тесный контакт между программистом и физической машиной не только давал возможность программисту уснащать его программу всеми видами трюков кодирования, он фактически побуждал его это делать. Для многих программистов этот соблазн становился непреодолимым; было даже время, когда все верили в то, что одним из наиболее жизненно важных качеств виртуозного программиста является то, что он должен быть "головоломным", и очень медленно осознавалось, что более существенен ясный и систематизированный смысл! Когда было в моде "изощренное программирование", программирование было не только очень дорогим - оно занимало слишком много времени, - также становилось очень трудно получить корректную программу. Глядя назад, период изощренного программирования представляется нам как поколение программистов, самоуверенно идущее по натянутому канату, поскольку они не знают о бездонной пропасти под ними! Современный компетентный программист более скромный и избегает хитрых трюков как чумы.

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

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

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

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

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

Проблема теперь состоит в том, что с одной стороны мы имеем аппаратную машину А, которая может быть построена, но программировать для которой нам не нравится потому, что это слишком громоздко, а с другой стороны мы имеем "машину нашей мечты", вымышленную машину В, программировать для которой мы любим, но инженеры не могут ее построить. Как нам преодолеть этот разрыв?

Разрыв преодолевается при помощи "программного обеспечения": имея машину А, мы можем сделать, раз и навсегда, программу (в машинном коде машины А), которая предписывает машине А шаблон поведения, следуя которому она будет имитировать машину В. Такая программа называется "программным обеспечением для машины А". Имея машину А, загруженную программным обеспечением, мы имеем механизм - отчасти аппаратный, отчасти программный, - который способен выполнять программы, написанные для вымышленной машины В.

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

Стандартное программное обеспечение, которое приходит с машиной защищает пользователя от зависимости от определенной машины; кроме того, оно вводит - так сказать, за спиной пользователя - стандартные пути работы с самыми сложными свойствами аппаратуры, такими как возможный параллелизм (т.е. параллельность во времени) вычислений и передача информации от и на периферийные устройства и многоуровневую память. До сих пор мы описывали аппаратное обеспечение так, как будто все ячейки памяти одинаково хорошо доступны для арифметического устройства. На практике это редкий случай, совершенно обычными являются два уровня памяти: первичная память (обычно на ферритовых сердечниках) и вторичная память (обычно на магнитных барабанах) [1971 год! - прим. перев.]. Только ячейки первичной памяти непосредственно и быстро доступны для арифметического устройства; информация во вторичной памяти (емкость которой на порядок больше, чем первичной памяти) для арифметического устройства непосредственно недоступны, но вместо этого имеется возможность групповой пересылки информации между первичной и вторичной памятью. В такой машине программное обеспечение может перемещать информацию в обе стороны между двумя уровнями памяти, все время отслеживая, где что в любой момент находится, и стараясь хранить в первичной памяти всю "текущую значимую" информацию. (Это называется "реализацией виртуальной памяти".)

Мы упомянули концепцию виртуальной памяти потому, что она связана с аспектом эффективности, которым программист отчасти управляет и в отношении которого он, следовательно, несет некоторую ответственность. Это называется "путаницей" (vagrancy). Программа имеет маленькую степень путаницы, если на большем периоде времени обращения сосредоточены на небольшом, плотном подмножестве общего объема информации; в этом случае обоснованной является надежда на то, что в этот период времени это плотное подмножество информации будет сохраняться в первичной памяти и, следовательно, вычисления могут идти на полной скорости. При вычислениях с путаницей возможность того, что нужная информация находится во вторичной памяти, намного большая, и возможности обмена между уровнями памяти имеют тенденцию стать узким местом. Следовательно, если это возможно, путаница должна быть устранена. [Американский термин для описываемой ЭВД ситуации - trash (толкотня), в русскоязычной специальной литературе часто употребляется без перевода: трэш. - прим. перев.]


НазадОглавлениеВперед
КаталогИндекс раздела