2 апреля 2021 года проект Fedora официально выступил против Ричарда Столлмана (RMS) и прекратил все отношения с Фондом свободного программного обеспечения (FSF).
Совет проекта Fedora пояснил, что организация поддерживает инклюзивные, доброжелательные и открытые сообщества разработчиков. Fedora против гонений, оскорблений, преследований, издевательств или другим форм злоупотреблений. Совет Fedora был ошеломлен тем, что FSF позволил Ричарду Столлману вернуться в совет директоров, учитывая его трансфобные высказывания и досаждающее поведение.
Совет проекта Fedora пояснил, что он обычно не участвует в управлении другими проектами. Однако это исключительный случай из-за того, что FSF контролирует развитие семейства лицензий GPL, которые имеют решающее значение для работы проекта.
Fedora, в соответствии с внутренними принципами и ценностями, прекращает предоставлять финансирование или участвовать в любых мероприятиях, спонсируемых FSF, и любых мероприятиях, на которых Ричард Столлман является ведущим докладчиком или экспонентом. Это также относится к любой организации, где он играет руководящую роль (проекта GNU).
25 марта Red Hat объявила, что против возвращения Столлмана. Компания прекратила финансирование Фонда свободного программного обеспечения (FSF). Ее сотрудники, а также многие разработчики и активисты СПО, поддерживающие решение Red Hat, не будут больше принимать участие в мероприятиях этой организации.
С 23 марта, после возвращения RMS в Фонд СПО, работа организации практически парализована из-за объявления бойкота и прекращения финансирования со стороны многих основных партнеров организации и ментейнеров, включая Fedora, Red Hat, Open Source Initiative (OSI), Software Freedom Conservancy (SFC), Европейского Фонда СПО, EFF (Electronic Frontier Foundation), Mozilla, Tor Project, FreeDOS, GNOME Foundation, X.org Foundation, HardenedBSD Foundation, MidnightBSD, Open Life Science, Open Source Diversity, Document Foundation (куратор LibreOffice), Creative Commons, GNU Radio, OBS Project, SUSE и даже создателя Rust Грейдона Хоара. Они обвиняют Столлмана в том, что он показал себя женоненавистником, эйблистом и трансфобом.
Против RMS уже подписали письмо многие ключевые команды разработчиков и ментейнеров. Сторонники Столлмана также сплотились, причем сейчас их голосов в письме в поддержку RMS больше практически на треть.
Что приходит Вам в голову, когда Вы слышите “низкоуровневое программирование”? Может быть, C++? Непрекращающийся контроль указателей, попытки оптимизации быстродействия, потребляемой памяти? Или, вероятно, вы представляете инструкции ассемблера какой-нибудь популярной ныне архитектуры?
Если же Вы вспоминаете, как перфоратором прокалывали перфоленту, одно отверстие за другим, в течение многих часов, то эта статья для Вас! Добро пожаловать под кат! :)
Lasciate ogni speranza, voi ch'entrate
— Dante Alighieri, La Divina Commedia
EDSAC — «Electronic Delay Storage Automatic Calculator», был создан в Кембридже в 1949 для военных нужд. Ныне в забвении, вспоминают о нём лишь в образовательных целях в N-й губернии в малоизвестной Alma Mater, дочери Петербурхского государственного унивеситета.
Существует также проект The EDSAC Replica Project, где могучая кучка энтузиастов создаёт эмулятор и гайды по EDSAC для сохранения памяти об этом удивительном устройстве.
One of the nicer features of the EDSAC is that it is conceptually a very simple machine.
— Tutorial Guide to the EDSAC Simulator
Слева Вы можете видеть текстовый редактор для инструкций. Инструкции бывают двух типов: Initial Orders 1 (далее IO1) и Initial Orders 2 (мега продвинутые). Измененную строку редактор подсвечивает жёлтым, зелёным — сохранённые. Комментарии заключаются в []. Редактор имеет память на несколько шагов назад, поэтому можно откатывать программу, нажимая Ctrl+Z. Всё это раньше набирали на перфоленте, это новодел. Первая инструкция в IO1 — T N S, где N — адрес последней строки с инструкцией + 1. Простейшая программа на EDSAC имеет вид:
Листинг 1. — EDSAC order code, простейшая программа.
Она не делает ничего. Как и большинство моих программ. Отсчёт начинается с 31, т. к. ячейки с 0..30 изначально используются для запуска самой машины, вспоследствии их можно использовать как ячейки памяти.
Кстати, по умолчанию симулятор настроен на IO2, чтобы переключить его на IO1, нажмите на врехней панели EDSAC -> IO1. Запускается по кнопке Start, останавливается по Stop, Single E. P. — пошаговая отладка, для этого надо написать вначале Z0S/ZS/Z0F/ZF.
Справа Вы можете симулятор и содержимое памяти вычислительной машины. Слова 17-битные, всего 1024 ячеек памяти (или 35-битные, 512 ячеек памяти, как будет удобнее, посередине — таинственный «sandwich-digit»). Еще есть аккумулятор (оперативная память), 71-бит, и 35-битный регистр умножения. Симулятор поддерживает ввод с помощью «дискового телефона», вывод на печать. Включив hints, вы можете смотреть на содержимое ячеек памяти, наводя на них мышкой. Сверху будет появляться число в 10СС. Также можно смотреть содержимое аккумулятора и регистра умножения.
О числах. Число записывается в память в дополнительном коде с помощью инструкции: P N S или P N L, где N — множитель перед двойкой. Например, P0S = 2*0+0=0, P0L = 2*0+1=1, P1S = 2, P1L = 3 и т. д. Отрицательные числа записываются в дополнительном коде, об этом можно почитать в туториале. EDSAC также работает с дробными числами. Символ короткого слова S (F для IO2) и L (D). Не только числа, но и инструкции меняют свой смысл в зависимости от этого.
Есть пара гайдов, основной и сокращённый, рекомендую последний т. к. там меньше буков понятнее изложен материал, работающие примеры. Чтобы сделать приведенный в них код работающим, надо заменить первую строку ZOS/ZS/Z0F/ZF на X0S/X0F. Инструкции смотрите там, либо уже в комментариях к коду.
В течение нескольких месяцев было изготовлено множество версий процедуры печати десятичных чисел. Если программист был непроходимо глуп, или был полным идиотом и совершенным «лозером», то подпрограмма конверсии отняла бы у него около сотни команд. Но любой хакер, стоивший своего имени, мог уместить ее в меньший объем. В конечном счете, попеременно убирая инструкции то в одном, то в другом месте, процедура была уменьшена до примерно пятидесяти инструкций.
— Стивен Леви, Хакеры: Герои компьютерной революции
Итак, моим заданием было написать программу по выводу n-й строки треугольника Паскаля.
Листинг 2. — Kotlin, вывод 3-й строки треугольника Паскаля.
fun main(args: Array<String>) {
var a = 1
var row = 3
row += 1
for (i in 1..row) {
print(a)
print(" ")
a = a * (row - i) / i
}
}
Здесь будет вложенный цикл, т. к. деление нужно реализовывать вручную.
Листинг 3. — EDSAC order code, целочисленное деление, округление вниз.
Как Вы могли заметить, адресация абсолютная, что доставляет головную боль, агрессию, вызывает скрип зубов, ненависть, разочарование, злобу, сложности (это пофиксили во 2 версии Initial Orders). При сдвиге одной строки сдвигается сразу МНОГО. И надо переписывать адреса всех «поплывших» строк.
[31] T154S [указатель на последнюю строку программы +1]
[32] E84S [безусловный переход к 84 строке, начало тела программы]
[33] PS [следующая цифра в десятичной СС]
[34] PS [степень десятки]
[35] P10000S [для корректного вывода чисел, степени 10, 10^4]
[36] P1000S [для корректного вывода чисел, степени 10, 10^3]
[37] P100S [для корректного вывода чисел, степени 10, 10^2]
[38] P10S [для корректного вывода чисел, степени 10, 10^1]
[39] P1S [для корректного вывода чисел, степени 10, 10^0]
[40] QS [для формирования чисел]
[41] #S [перевод на цифровой регистр]
[42] A40S [загрузка в аккумулятор символа формирования чисел]
[43] !S [символ пробела]
[44] &S [символ переноса строки на новую]
[45] @S [символ возврата каретки]
[46] O43S [печать телепринтером пробельного символа]
[47] O33S [печать телепринтером следующей цифры числа в 10СС]
[48] PS [число для вывода]
[49] A46S [загружаю в аккумулятор команду печати пробела для исполнения из 46 ячейки]
[50] T65S [загружаю в ячейку памяти 65, обнуляю аккумулятор]
[51] T300S [обнуляю аккумулятор]
[52] A35S [загружаю в аккумулятор число из ячейки памяти 35, 10000<<1]
[53] T34S [загружаю его в ячейку памяти 34, обнуляю аккумулятор]
[54] E61S [если число в аккумуляторе >= 0, перехожу к строке 61]
[55] T48S [загружаю новое значение числа для вывода в ячейку 48, обнуляю аккумулятор]
[56] A47S [загружаю число из 47 ячейки в аккумулятор]
[57] T65S [загружаю число из аккумулятора в 65, обнуляю аккумулятор]
[58] A33S [загружаю число из 33 ячейки в аккумулятор]
[59] A40S [прибавляю число из 40 ячейки к содержимому аккумулятора]
[60] T33S [загружаю число из аккумулятора в 33, обнуляю аккумулятор]
[61] A48S [загружаю число для вывода из ячейки памяти 48 в аккумулятор]
[62] S34S [вычитаю текущую степень десятки]
[63] E55S [если число >= 0, повторяю, перехожу к строке 55]
[64] A34S [загружаю степень десятки из ячейки 34 в аккумулятор]
[65] PS [цифра или пробел, в зависимости от ситуации]
[66] T48S [загружаю число из аккумулятора в ячейку 48, там число для вывода]
[67] T33S [загружаю число из аккумулятора в ячейку 33, там следующая цифра
десятичной записи числа]
[68] A52S [загружаю инструкцию из ячейки 52 в аккумулятор]
[69] A4S [прибавляю 1]
[70] U52S [записываю содержимое аккумулятора в ячейку 52]
[71] S42S [вычитаю из аккумулятора символ формирования чисел]
[72] G51S [если число в аккумуляторе < 0, перехожу к 51 ячейке]
[73] A103S [загружаю инструкцию из ячейки 103 в аккумулятор]
[74] T52S [загружаю содержимое аккумулятора в ячейку 52, обнуляю аккумулятор]
[75] PS [ячейка памяти для хранения инструкции возврата]
[76] PS [ячейка памяти для хранения индекса цикла]
[77] PS [const = 0]
[78] PS [const = 0]
[79] PS [const = 0]
[80] E100S [переход к строке 100]
[81] E104S [переход к строке 104]
[82] P5S [показатель треугольника Паскаля, вводится просто как число между P и S,
по правилам обычной арифметики в 10СС]
[83] E123S [переход к выводу переноса строки, строка программы 123]
[84] A110S [загружаю в аккумулятор число 2]
[85] T30S [выгружаю в ячейку памяти 30, обнуляю аккумулятор]
[86] O41S [перевод на цифровой регистр телепринтера]
[87] T300S [обнуляю аккумулятор]
[88] O44S [печатаю переноса строки на новую]
[89] O44S [печатаю переноса строки на новую]
[90] A76S [загружаю в аккумулятор индекс цикла]
[91] A4S [увеличиваю его на 1]
[92] T76S [перезаписываю изменённый индекс, обнуляю аккумулятор]
[93] E113S [безусловный переход к ячейке 113]
[94] T300S [обнуляю аккумулятор]
[95] A30S [загружаю в аккумулятор число из 30 ячейки, обнуляю аккумулятор]
[96] T48S [выгружаю в ячейку памяти 48, обнуляю аккумулятор]
[97] A80S [загружаю в аккумулятор инструкцию перехода из ячейки 80]
[98] T75S [загружаю её в 75 ячейку, обнуляю аккумулятор]
[99] E49S [перехожу к печати текущего числа]
[100] A81S [загружаю в аккумулятор инструкцию перехода из ячейки 81]
[101] T75S [выгружаю в ячейку памяти 75, обнуляю аккумулятор]
[102] E49S [перехожу к печати текущего числа]
[103] A35S [перехожу к печати текущего числа]
[104] A76S [загружаю в аккумулятор индекс цикла из ячейки 76]
[105] S82S [вычитаю показатель треугольника Паскаля]
[106] S110S [вычитаю 2]
[107] G87S [если полученное число <0, перехожу к строке 87]
[108] X0S [пустая команда]
[109] ZS [конец программы, сигнальный звонок]
[110] P1S [const = 2]
[111] P2S [const = 4]
[112] P0L [const = 1]
[113] T300S [обнуляю аккумулятор]
[114] A76S [загружаю в аккумулятор содержимое 76 ячейки]
[115] S111S [вычитаю содержимое 111 ячейки, 4]
[116] E124S [если число в аккумуляторе >=0, перехожу к 124 ячейке]
[117] T300S [иначе обнуляю аккумулятор]
[118] A30S [загружаю в аккумулятор число из 30 ячейки]
[119] T48S [загружаю в ячейку памяти 48, число для вывода, обнуляю аккумулятор]
[120] A83S [загружаю в аккумулятор инструкцию из адреса 83]
[121] T75S [загружаю ее в 75 ячейку]
[122] E49S [перехожу к печати числа из памяти]
[123] O44S [печатаю переноса строки на новую]
[124] O44S [печатаю переноса строки на новую]
[125] T300S [обнуляю аккумулятор]
[126] A82S [загружаю в аккумулятор показатель треугольника Паскаля]
[127] A110S [добавляю 2]
[128] S76S [вычитаю индекс цикла]
[129] T29S [записываю содержимое аккумулятора в ячейку 29, обнуляю аккумулятор]
[130] H29S [загружаю в умножающий регистр число из 29 ячейки]
[131] V30S [умножаю на число из 30 ячейки, результат в аккумуляторе]
[132] L64S [сдвигаю аккумулятор влево на 8 ячеек]
[133] L32S [сдвигаю аккумулятор влево на 7 ячеек]
[134] T30S [выгружаю число из аккумулятора в 30 ячейку памяти]
[135] T2S [выгружаю частное в ячейку памяти 2, обнуляю аккумулятор]
[136] A30S [загружаю делимое в аккумулятор из ячейки 30]
[137] T1S [записываю делимое в 1 ячейку, обнуляю аккумулятор]
[138] A1S [читаю содержимое делимого из ячейки памяти 1, начало цикла]
[139] S76S [вычитаю делитель из ячейки памяти 76]
[140] T1S [записываю новое значение делимого в ячейку 1, обнуляю аккумулятор]
[141] A2S [загружаю частное из ячейки 2]
[142] A110S [прибавляю 1 из ячейки памяти 110]
[143] T2S [записываю увеличенное на 1 частное в ячейку 2, обнуляю аккумулятор]
[144] A1S [выгружаю в аккумулятор делимое из ячейки 1]
[145] G147S [если делимое < 0, стоп, переход на строку 147]
[146] T1S [иначе записываю содержимое аккумулятора в ячейку 1, обнуляю аккумулятор]
[147] E138S[если делимое >= 0, повторять цикл деления, переход на 138 строку, конец цикла]
[148] T300S [обнуляю аккумулятор]
[149] A2S [выгружаю частное из ячейки 2]
[150] S110S [вычитаю 1 из ячейки памяти 110]
[151] U2S [загружаю частное обратно в ячейку 2]
[152] T30S [загружаю частное в ячейку 30, обнуляю аккумулятор]
[153] E94S [безусловный переход к строке 94, подготовка к выводу числа]
Что сказать про IO2? Поддерживает подпрограммы, относительную адресацию, набор команд расширен.
Та же программа на IO2:
..PK
T56K
[P6, подпрограмма для вывода чисел из стандартной библиотеки]
GKA3FT25@H29@VFT4DA3@TFH30@S6@T1F
V4DU4DAFG26@TFTFO5FA4DF4FS4F
L4FT4DA1FS3@G9@EFSFO31@E20@J995FJF!F
..PZ
[программа для нахождения n-й строки треугольника Паскаля]
GK [фиксирую абсолютное значение ячейки памяти для относительной индексации]
[0] XF [для отладки, нулевая инструкция]
[1] O34@ [вывод переведён на цифровой регистр, загрузка данных для печати из ячейки 34]
[2] O35@ [вывод новой строки, загрузка данных для печати из ячейки 35]
[3] O36@ [возврат каретки телепринтера, загрузка данных для печати из ячейки 36]
[4] TF [обнуление аккумулятора]
[5] A27@ [значение из ячейки 27 добавляется в аккумулятор]
[6] TF [запись этого значения в 0 ячейку для вывода, обнуление аккумулятора]
[7] A7@ [загрузка замкнутой подпрограммы по выводу числа]
[8] G56F [печать числа с использованием P6]
[9] T20@ [зануляю 20 ячейку памяти]
[10] A28@ [загружаю в аккумулятор индекс цикла]
[11] A31@ [добавляю 1]
[12] U28@ [обновляю значение индекса цикла в ячейке 28]
[13] T20@ [записываю значение индекса цикла в ячейку 20, обнуляю аккумулятор]
[14] A33@ [загружаю показатель в аккумулятор]
[15] A31@ [+1]
[16] S28@ [вычитаю текущий индекс из ячейки 28]
[17] T20@ [записываю значение индекса цикла в ячейку 20, обнуляю аккумулятор]
[18] E37@ [безусловный переход к умножению, ячейка 37]
[19] PD [const = 1]
[20] XF [нулевая инструкция]
[21] XF [нулевая инструкция]
[22] A33@ [загружаю показатель из ячейки 33 в аккумулятор]
[23] A31@ [+1]
[24] S28@ [вычитаю содержимое 28 ячейки, индекс]
[25] E2@ [если >= 0, повторяю цикл, переход к строке 2]
[26] ZF [конец программы, сигнальный звонок]
[27] PD [число для вывода]
[28] PF [индекс цикла]
[29] PD [используется в подпрограмме для печати]
[30] PD [используется в подпрограмме для печати]
[31] PD [const = 1]
[32] P1F [символ перехода на новую строку]
[33] P2D [показатель треугольника Паскаля, записывается по правилам EDSAC]
[34] #F [символ перевода вывода на цифровой регистр]
[35] @F [символ возврата каретки]
[36] &F [символ переноса строки на новую]
[37] H20@ [загружаю в умножающий регистр число из 20 ячейки]
[38] V27@ [умножаю на число из 27 ячейки, результат в аккумуляторе]
[39] L1024F [сдвигаю аккумулятор влево на 12 ячеек]
[40] L4F [сдвигаю аккумулятор влево на 4 ячеек]
[41] T20@ [выгружаю число из аккумулятора в 20 ячейку памяти, обнуляю аккумулятор]
[42] T102@ [выгружаю частное в ячейку памяти 102, обнуляю аккумулятор]
[43] A20@ [загружаю делимое в аккумулятор из ячейки 20]
[44] T101@ [записываю делимое в 101 ячейку, обнуляю аккумулятор]
[45] A101@ [читаю содержимое делимого из ячейки памяти 101, начало цикла]
[46] S28@ [вычитаю делитель из ячейки памяти 28]
[47] T101@ [записываю новое значение делимого в ячейку 101, обнуляю аккумулятор]
[48] A102@ [загружаю частное из ячейки 102]
[49] A31@ [прибавляю 1 из ячейки памяти 31]
[50] T102@ [записываю увеличенное на 1 частное в ячейку 102, обнуляю аккумулятор]
[51] A101@ [выгружаю в аккумулятор делимое из ячейки 101]
[52] G54@ [если делимое < 0, стоп, переход на строку 54]
[53] T101@ [иначе записываю содержимое аккумулятора в ячейку 101, обнуляю аккумулятор]
[54] E45@ [если делимое >= 0, повторять цикл деления, переход на 45 строку, конец цикла]
[55] T200@ [чищу содержимое аккумулятора]
[56] A102@ [выгружаю частное]
[57] S31@ [вычитаю 1 из ячейки памяти 102]
[58] U102@ [загружаю частное обратно в ячейку 102]
[59] T27@ [загружаю частное в ячейку 27, обнуляю аккумулятор]
[60] E22@ [безусловный переход к строке 22, подготовка к выводу числа]
[61] EZPF [указатель на исполнение кода]
Рис 4. — Вывод для показателя = 5.
Надеюсь, Вам понравилась эта статья и Вы прониклись истоками программирования. Когда пишешь подобный код, начинаешь ценить все те уровни абстракции, которые появились впоследствии, понимаешь, какой же большой путь прошли ЯП, чтобы стать такими, какие они есть. Спасибо за Ваше время!
P. S. Милейшие друзья из губернии N, не стоит благодарности за любезно предоставленный мной код, если у Вас совпал со мной вариант. Удачи с RISC-V!
P. P. S. Прекрасные примеры кода для EDSAC IO2 приведены здесь.
P. P. P. S. Код публикую под лицензией MIT.
Copyright 2021, ALEKSEI VASILEV
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the «Software»), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED «AS IS», WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Пример дефекта шлейфа матрицы экрана в моделях MacBook Pro 2016 и 2017 года. Из-за разрыва короткого и тонкого кабеля подсветка в нижней части дисплея работает нештатно (эффект «сценического света»). Через некоторое время она полностью выходит из строя.
По информации MacRumors, суд признал, что Apple знала о дефекте Flexgate в продаваемых в 2016-2017 годах ноутбуках MacBook Pro. Cудья окружного суда Северной Калифорнии посчитал, что производитель намеренно продавал в течение полутора лет неисправные устройства, так как все продукты Apple проходят несколько этапов тестирования до попадания на прилавок.
Решение по групповому иску пользователей против Apple еще не принято, но заявление суда подтверждают правоту пострадавшей стороны. Apple со своей стороны продолжает отстаивать свою позицию, что в шлейфе подстветки все было в порядке и массового заводского дефекта там не было.
Каждый раз, когда пользователи ноутбуков MacBook Pro 2016-2017 годов выпуска открывали и закрывали устройство, шлейф экрана поддавался незначительному трению и нагрузкам. Со временем этот элемент начинал перетираться, потом подсветка экрана вообще переставала работать. В своем иске пострадавшая сторона утверждает, что этому подвержены абсолютно все ноутбуки, но проблема начала проявляться спустя годы использования. В случае с MacBook Pro, исходя из имеющейся статистики, проблема проявлялась чаще всего через полтора года использования, когда срок годовой гарантии уже истек.
Вдобавок на заседании суда представитель пострадавшей стороны заявил, что Apple агрессивно ведет себя по отношению с пользователями, столкнувшимися с этой проблемой. Компания неправомерно модерировала и даже удаляла темы и сообщения этих пользователей на своем форуме. Судья по существу этого заявления пояснил, если оно подтвердиться, что Apple практически предоставила ему еще одно доказательство того, что компания была в курсе проблемы.
В групповом иске против Apple истцы требуют возместить им все расходы, понесенные за ремонт бракованных ноутбуков, так как он выполнялся не по гарантии, а также компенсировать все судебные издержки. Также в иске указано, что Apple должна бесплатно отремонтировать все остальные проданные MacBook Pro 2016 и 2017 года, фактически начав глобальную отзывную кампанию. Ранее Apple бесплатна ремонтировала только MacBook Pro 13", которые были подвержены этой проблеме.
Этим материалом мы начинаем цикл статей о решении VMware SD-WAN. Сегодня поговорим о том, какие рыночные предпосылки сформировали его появление, какие задачи решает SD-WAN и каковы технические особенности решения VMware.
Появление SD-WAN
До облачной революции, когда деревья были большими, а про облака говорили мало и редко, для размещения приложений большинство компаний использовали локальные ЦОДы. Доступ для сотрудников из удаленных офисов предоставлялся через выделенные каналы связи — MPLS. При таком подключении большинство пользователей из филиалов прямого пути в большую сеть не имели и выходили за пределы приватной сети через центральный офис, который мог находиться за тысячи километров от филиала и пользователя.
Чуть позже компании, наконец, распробовали облачные технологии. Бизнес стал размещать свои приложения в облаках, а доступ к ним организовывать через интернет, без использования выделенных каналов. Требования к пропускной полосе и скорости обмена информацией постоянно росли. Если со старыми корпоративными приложениями, не относящимися к медиа или ПО реального времени, традиционный подход еще работал, то по мере появления видеохостингов и сервисов видеосвязи, взрывной рост популярности которых пришелся на начало 2010-х, стало очевидно: нужно что-то менять.
Выделенные MPLS-каналы со своей низкой пропускной способностью и высокой стоимостью (за 1 Мбит на MPLS приходилось платить иногда в десятки раз больше, чем за такой же канал в интернет) банально перестали отвечать требованиям рынка.
Казалось бы, почему не перейти на интернет? Это дешево и сердито, а скорость доступа к облакам выше. Ответ прост: в случае с интернетом невозможно гарантировать качество сервиса. Да, MPLS дорогой и неудобный, но провайдеры как минимум на уровне контракта гарантируют качество. А в случае с интернетом обещать, что приложения, физически находящиеся бог знает где, через конкретного провайдера будут хорошо работать, нельзя. Сами сервисы находятся вне зоны ответственности провайдера.
Иными словами, те, кто отвечают за канал связи, не отвечают за приложения, и наоборот. Заказчик оказывается между Сциллой и Харибдой.
Решения SD-WAN долгое время оставались уделом стартапов. В стадию зрелости технология перешла всего несколько лет назад, когда крупные компании начали активно приобретать специализированные проекты. VMware выкупила стартап Velocloud, который делил первое место на рынке с аналогичным проектом — Viptela, примерно в то же время купленным Cisco. Еще пару лет заняла консолидация: одни стартапы разорялись — не всем удавалось сохранить нужные темпы роста и найти свое место на рынке, наиболее перспективные и успешные переходили под крыло крупных вендоров.
Чтобы нарастить конкурентные преимущества, каждой компании пришлось найти собственную уникальную нишу. Решение Velocloud создавалось для того, чтобы бизнес мог получать надежный и качественный доступ к приложениям независимо от того, где они находятся: в локальном дата-центре или в облаке, доступ в которое осуществляется через интернет. Спойлер: реализовать это удалось благодаря архитектурному компоненту решения — SD-WAN Cloud Gateways. Идея состоит в том, что облачные технологии можно использовать и в мире сетевых подключений.
Как работает SD-WAN
Ранее сетевые устройства (коммутаторы, маршрутизаторы и прочие компоненты классической архитектуры) настраивались независимо друг от друга. Модули обработки данных, которые пересылают пакеты между сетевыми портами, интерфейсы управления — веб-интерфейсы, командная строка, а также служебные сетевые сервисы, которые помогают разным устройствам между собой «договариваться» — все это требовалось отдельно конфигурировать для каждого устройства в сети.
Этот неудобный подход просуществовал вплоть до появления SDN. Технология программно-определяемых сетей позволила централизовать control plane и management plane, что дало существенный выигрыш в плане управляемости и масштабируемости сети. Стало возможным без труда управлять распределенной сетью из сотен и даже тысяч устройств, выросла скорость внесения изменений, а также появилась возможность более интеллектуальной обработки потоков данных.
Концепция SDN подразумевает, что «мозг» всех устройств объединяется единой сервисной платформой. Это может быть облачный сервис или физический компонент, который управляет работой устройств. Применение этих принципов к глобальным сетям позволило сделать сеть более гибкой и управляемой.
Несмотря на то, что концепция SDN одна, реализована она у всех вендоров по-своему. Различные решения отличаются между собой задачами, под которые они разрабатывались, используемыми для этого компонентами и, соответственно, конечной архитектурой.
Далее мы подробно рассмотрим все компоненты VMware SD-WAN и поговорим о каждом из них отдельно.
Компоненты решения
VMware SD-WAN Orchestrator
SD-WAN Orchestrator — это облачный портал управления, мониторинга и диагностики сети. С его помощью можно буквально в один клик запустить виртуальные сетевые службы в филиале, облаке или в корпоративном ЦОДе. Фактически, это веб-портал управления сетью SD-WAN, который можно получить как SaaS или развернуть в локальном ЦОД.
Архитектура оркестратора позволяет легко масштабировать сеть с нескольких узлов до нескольких тысяч. Управление на основе политик и профилей-шаблонов позволяет за минуты распространять изменения в сети. Этим функционал оркестратора не ограничивается.
Мониторинг. Данные о состоянии узлов сети, SLA по каналам, сетевая активность по приложениям и клиентам, а также продвинутая система построения отчетов
Zero-Touch Provisioning (ZTP). Активация оконечного устройства в филиале не требует выезда отдельного специалиста на площадку. Весь процесс укладывается в несколько простых шагов. Сотрудник в удаленном офисе подключает к устройству все кабели и включает его. Через интерфейс оркестратора создается новый Edge и высылается email со ссылкой для активации. Там «зашиты» нужные параметры для настройки WAN-интерфейса оборудования. Сотрудник в филиале, подключившись через провод или WiFi с планшета, переходит по этой ссылке, а устройство получает автоматически связность с оркестратором, вне зависимости от типа подключения к интернету.
Диагностика сети. В панели управления можно посмотреть состояние протоколов маршрутизации, портов, таблиц ARP и NAT, запустить Ping или Traceroute и многое другое без необходимости подключения к устройствам через CLI/SSH.
Журнал событий. Изменения и уведомления со всех устройств доступны для поиска и просмотра на SD-WAN Orchestrator.
Автоматизация через API. SD-WAN Orchestrator предоставляет REST API, примеры использования можно посмотреть на SD-WAN Dev Center.
Посмотрим, как это выглядит.
SD-WAN Dashboard — общий вид на всю сеть:
Список филиалов и их расположение на карте:
Статистика по приложениям и клиентам:
Информация о качестве каналов — Quality of Experience:
Детальные характеристики каналов:
VMware SD-WAN Gateway
SD-WAN Gateways — это сетевые шлюзы, развернутые в высоконадежных ЦОД по всему миру. В первую очередь, они выполняют функцию Control Plane, но в зависимости от задачи могут брать на себя сразу обе плоскости — и control plane, и data plane. Эти шлюзы позволяют соединить между собой разрозненные офисы и обеспечивают оптимальный путь от филиала к данным и приложениям в ЦОД и облаках. Потребность в установке мощных концентраторов или устройств координации непосредственно в филиалах отпадает.
На уровне Control Plane обеспечивается и обмен маршрутной информацией между филиалами, так как Gateway выполняет роль рефлектора маршрутов. Филиалы получают обновления маршрутов от Control Plane, благодаря чему настраивать протоколы маршрутизации между устройствами Edge не требуется.
Ещё одна важная роль SD-WAN Gateway — помощь в определении характеристик каналов связи. При активации устройства Edge выполняется подключение к Gateway и замер полосы пропускания канала, также автоматически определяется MTU.
По своему принципу работы эти шлюзы мультитенантные, то есть предназначены для обработки данных большого количества клиентов сразу. Благодаря тому, что сеть этих мультитенантных шлюзов распределена по множеству дата-центров, SD-WAN легко масштабировать от пары площадок до нескольких тысяч. Об отказоустойчивости можно не беспокоиться — каждое устройство Edge подключается как минимум к двум шлюзам в разных регионах.
При этом приложения, с которыми работают пользователи, могут быть развернуты как в облаке провайдера, так и на собственной площадке клиента. Если инфраструктура компании — филиалы, офисы — распределена географически, каждый офис будет подключаться к ближайшему SD-WAN Gateway.
Для интернет-подключений шлюз выполняет ту же задачу — обеспечивает интеллектуальную обработку трафика, соблюдение SLA и гарантирует, что приложение будет работать с максимально возможной для используемых каналов производительностью.
Хороший пример — использование облачных сервисов совместной работы, например, Microsoft Office 365, Dropbox, Zoom, Skype for business и других. В месте их фактического расположения установить SD-WAN Edge невозможно, но получить преимущества SD-WAN Overlay (подробнее — в разделе о протоколе VCMP) нужно. SD-WAN Gateway выполняет функцию шлюза в «большой» интернет с балансировкой между каналами, компенсацией ошибок и выбором наиболее оптимального канала для передачи пакетов конкретного приложения.
VMware SD-WAN Edges
VMware SD-WAN Edge — это физическое или виртуальное устройство с функциями безопасности. Фактически, это маршрутизатор, который может как заменить уже размещенные в филиалах устройства, так и работать с ними в тандеме. Он поддерживает функции сетевого шлюза, Site-to-Site VPN, DHCP-сервера, NAT и файрвола, а также различные протоколы маршрутизации для интеграции с физической сетью.
SD-WAN Edge в виртуальном исполнении подойдет тем, кто планирует использовать концепцию Software-Defined Branch или обеспечивать доступ к своим приложениям в ЦОД через SD-WAN. Виртуальный Edge в ЦОД подходит как в качестве связующего звена ресурсов ЦОД и удаленных площадок, так и в роли хаба для агрегации и распределения трафика внутри сети SD-WAN.
SD-WAN Edge поддерживает основные варианты резервирования — есть возможность объединения в отказоустойчивую пару или подключение по протоколу VRRP.
И если компоненты решения — руки, ноги и голова технологии VMware SD-WAN, то его сердце — технология DMPO. Именно она и позволяет реализовать уникальные фишки по обработке сетевых пакетов при передаче данных (между филиалами и ЦОД, филиалами и облаками) и обеспечивает высокое качество связи. Поговорим о ней подробнее.
Технология DMPO
Технология DMPO (Dynamic Multi-Path Optimization) позволяет нам за счет гибкого управления трафиком на уровне приложений агрегировать потоки и обеспечивать высокое качество связи. DMPO работает на уровне отдельных пакетов, однако учитывает, к каким приложениям эти пакеты относятся. Транзакционным приложениям, таким как веб-сайты, инструменты передачи файлов, не принципиально, придет пакет вовремя или чуть позже, главное, чтобы он вообще дошел. А приложениям реального времени (стриминговые сервисы, приложения для голосовых вызовов и т.п.) очередность пакетов важна. Из-за мелких неполадок может «рассыпаться» картинка на экране или собеседник не расслышит слово. Поэтому важно, чтобы SD-WAN устройства понимали, что это за приложение. В этом помогает движок распознавания приложений (Deep Application Recognition), работающий на каждом устройстве SD-WAN Edge.
Приложений существует огромное множество, 3000+ из них есть во встроенной базе DPI VMware SD-WAN. Кроме того, можно добавить и кастомные (самописные) сигнатуры, если вдруг какого-то приложения не хватает. Посмотрим, какие функции выполняет DMPO.
Непрерывный мониторинг качества каналов
Для мониторинга состояния WAN-каналов в реальном времени стандартные сетевые технологии вроде BFD или ICMP Probes не совсем подходят, т.к. тестовые пакеты отличаются от пользовательского трафика (другие классы DSCP, другой размер пакетов), а интервал проб слишком высок, чтобы достоверно и быстро определить ухудшение качества. Например, даже при замерах BFD или ICMP каждую секунду за время между этими замерами у обычного пользователя с ноутбуком передается 200+ пакетов (проверьте сами, сняв дамп трафика). Чтобы зафиксировать уровень потери в 2%, потребуется не менее 50 BFD пакетов (50 с). Если использовать более «агрессивные» значения, например, 100 мс, все равно потребуется не менее 5 секунд, чтобы обнаружить потери в 2% на канале. За это время тысячи пакетов с пользовательскими данными могут быть потеряны.
Поэтому в решении Velocloud используется собственный протокол инкапсуляции — Velocloud Multipath Protocol (VCMP), который добавляет к каждому пакету специальные заголовки, помогающие отследить время его прохождения (Latency, Jitter) и факт доставки. Это позволяет обнаружить ухудшение качества каналов практически мгновенно, а затем перенаправить пакеты на канал с лучшими показателями незаметно для пользователя.
За точное время в сети отвечает Control Plane — уже известные нам SD-WAN Gateways. Устройства SD-WAN Edge синхронизируют свое время с центром управления сетью. Таким образом можно гарантировать, что все временные метки будут достоверны. В стандартных сетевых протоколах такие возможности отсутствуют.
Состояние каналов отслеживается даже в том случае, если пользовательского трафика на данный момент нет. Ведь когда этот трафик появится, его нужно сразу же отправить по оптимальному пути. Для этого устройства SD-WAN Edge обмениваются служебными пакетами VCMP, только без User Payload, каждые 100 или 500 мс. Как только появляется пользовательский трафик, система снова начинает отслеживать SLA для каждого пакета.
Динамическая балансировка трафика и агрегация каналов
DMPO позволяет определять, какие приложения используют сеть, при помощи политик манипулировать поведением SD-WAN и настраивать приоритеты.
Решение VMware умеет автоматически (за счет работы overlay-протокола VCMP) выбирать наилучший способ обработки и доставки трафика приложений. К примеру, если конкретному приложению требуется ускорить передачу данных, то есть агрегировать несколько каналов, «умное» SD-WAN устройство начнет передавать данные сразу по нескольким путям. Для балансировки используются все доступные подключения.
В ряде случаев стандартная балансировка не имеет смысла, они сильно отличаются между собой по характеристикам: на одном задержка 5 мс, на другом — 100). Соответственно, балансировать трафик между ними бесполезно. Однако если «быстрый» канал окажется забит под завязку и на нем скопится большая очередь из пакетов, система определит, что каналы «сравнялись» по времени доставки пакетов. В этом случае в рассылке пакетов будут задействованы оба канала таким образом, чтобы пакеты по обоим доходили до точки назначение в одно и то же время. На принимающей стороне пакеты выстраиваются в порядке очереди так, чтобы клиенту или серверу за пределами сети SD-WAN не приходилось пересобирать их в требуемой последовательности. Соответственно, еще одна из функций, которую способно обеспечить устройство SD-WAN — автоматический реордеринг.
Поскольку алгоритмы работают с отдельными пакетами, при работе с приложениями, ведущими двунаправленную передачу данных, могут возникать достаточно интересные ситуации. Предположим, имеется два хоста — клиент и сервер, которые находятся в разных филиалах, или сервер в дата-центре и клиент-пользователь в филиале, между ними два канала связи от разных операторов. Что-то идет от сервера к клиенту, что-то обратно — такой обмен считается одной сетевой сессией.
Из-за особенностей каналов связи трафик между точками А и В может идти неравномерно: в одну сторону с задержкой 10 мс, обратно — 20 мс. В то же самое время канал работает наоборот: «туда» — 20 мс, «сюда» — 10 мс задержки. По сети VMware SD-WAN такая сессия будет передаваться по обоим каналам сразу, в одну сторону по каналу с задержкой 10 мс, и так же обратно — по каналу с наилучшими характеристиками.
За счет того, что каждый пакет обрабатывается индивидуально алгоритмами DMPO, система выбирает наилучший путь для каждого пакета автоматически, принимая решения в реальном времени.
Раньше администраторам приходилось вручную определять эту логику, либо настраивать схемы вида: «каждые N секунд проверяем, какой канал и в какую сторону работает лучше и перестраиваемся при необходимости». Реализовать такую схему без разрывов пользовательских сессий практически невозможно. Ценность SD-WAN заключается в том, что вся эта запутанная логика делегирована алгоритмам — они быстрее и точнее, чем даже самые сложные проверочные скрипты. Логика VMware SD-WAN работает на разных типах сессий и на разных транспортных протоколах, никак не нарушая логику работы TCP/IP.
Технологии корректировки ошибок
Выше мы упомянули, что каждый входящий пакет «маркируется» своим номером в очереди. Соответственно, сетевые устройства знают, в какой последовательности пакеты должны приходить, их можно легко отбалансировать по разным каналам и пересобрать в правильном порядке, когда они дойдут до получателя. Также за счет этого легко обнаружить потери и в случае необходимости запросить у отправляющей стороны пропущенный пакет данных:
Получатель:Так, к нам идут пакеты. Первый, второй, третий, пятый… Стоп. Почему пятый? Требую выслать мне пакет №4 как можно скорее! Прием!
Отправитель:Посмотрим, к какому приложению относился этот ваш пакет. Ага, вот оно! Высылаю компенсацию, подтвердите получение!
В случае транзакционного приложения устройству-отправителю будет достаточно найти в буфере нужный пакет и повторить отправку — так работает технология NACK (Negative Acknowledgement). В устройствах SD-WAN используется серверная память гораздо большего объема, чем в обычных маршрутизаторах. Самая младшая модель имеет на борту 4 Гб, на старших — 32 Гб и выше. Это позволяет держать буфер данных и по необходимости досылать потерявшиеся пакеты.
С realtime-приложениями ситуация обстоит иначе. Повторно отправлять пакеты не имеет смысла, важно предотвратить потери в будущем. Поэтому устройство-отправитель включает упреждающуюкоррекцию ошибок (Forward Error Correction, FEC), и каждый пакет начинает передаваться в двух экземплярах. Таким образом повышается вероятность успешной доставки пакетов, а лишние автоматически отбрасываются на принимающей стороне. На практике это позволяет использовать телефонию и видеоконференции на каналах с потерями до 30-40%.
Под капотом у провайдера
Некоторые компании пытаются внедрять SD-WAN самостоятельно, что приводит к необходимости решать множество не относящихся к сетевым технологиям задач (проектирование и развертывание управляющих компонентов, резервирование, масштабирование и дальнейшая поддержка, защита от атак, обеспечение доступности и т.д.). Согласно мировому опыту, большинство компаний все же выбирают SD-WAN как услугу от облачного провайдера или оператора связи. Давайте посмотрим, в чем плюсы использования SD-WAN от провайдера.
Разделение зон ответственности
Первая задача, с которой столкнется компания при самостоятельном внедрении — построение облачной инфраструктуры SD-WAN: деплой контроллеров, виртуальных машин, выделение ресурсов, администрирование и многое другое. Построение такой инфраструктуры повлечет за собой значительные расходы в виде выделения или покупки дополнительных серверов и ПО, а также обеспечения условий Disaster Recovery. Эти сложности и расходы особенно трудно обосновать при постепенном внедрении SD-WAN. Во-вторых, для построения надежной отказоустойчивой сетевой связности требуется детально проработать целевую схему подключения, определить движение потоков данных, настроить политики безопасности.
Когда вы обращаетесь к провайдеру, все эти задачи ложатся на него. Он обеспечивает инфраструктуру, круглосуточный мониторинг, администрирование сети и защищенных распределенных ЦОД, уровень доступности и выполнение SLA. Провайдер поможет выбрать оптимальное оборудование и даст рекомендации по модернизации сети, чтобы она соответствовала всем требованиям качества и надежности. При этом клиенту остается только запросить доступ к облачному порталу управления сетью и создать учетные записи для администраторов. На любые вопросы по использованию услуги и настройке сервисов SD-WAN поможет ответить круглосуточная русскоязычная техническая поддержка.
Оркестратор и сетевые шлюзы находятся в зоне ответственности провайдера и разворачиваются на его инфраструктуре. Заказчику понадобится только установить на своей стороне Edge. Арендовать его можно прямо у провайдера — при необходимости он поможет и с установкой.
Распределенная инфраструктура провайдера
Общая инфраструктура «ИТ-ГРАД» и #CloudMTS насчитывает 10 ЦОД в СНГ и множество точек присутствия по всему миру. Где бы ни находился филиал, он будет подключен к ближайшему SD-WAN Gateway, что позволит минимизировать задержки и оптимизировать доступ к любым приложениям — в локальных ЦОД и любых облаках. Наличие распределенной инфраструктуры также позволяет обеспечивать георезервирование для сети SD-WAN любого масштаба.
Инфраструктура «ИТ-ГРАД» и #CloudMTS на территории РФ
В каждом ЦОД резервируются каналы, обеспечивается высокопроизводительная связность шлюзов SD-WAN с внешним миром. Дата-центры подключены к глобальной опорно-магистральной сети МТС, которая обеспечивает доступ к мировым публичным точкам обмена трафиком — DE-CIX (Франкфурт), AMS-IX (Амстердам), HK-IX (Гонконг), LINX (Лондон), Equinix, NY-IX и т.д.
Кроме того, в распоряжении наших заказчиков:
PNI (Private Network Interconnect) с гиперскейлерами и крупными генераторами трафика. Среди них Amazon AWS, Microsoft, Google Cloud, Apple, Facebook, Valve, Twitch, Netflix, Twitter и прочие.
CDN-сети: Akamai, Google Cash и Google CDN, Cloudflare, Gcore labs, Microsoft и прочие.
Интеграция с IaaS и другими сервисами
Использование SD-WAN позволяет получить надежный доступ к сервисам, расположенными в инфраструктуре облачного провайдера. Какие преимущества это дает:
VDI, Skype for Business — компенсация ошибок, умная балансировка пакетов между всеми каналами, подключенными к площадке. Использование решения обеспечивает гарантированное качество для сервисов реального времени за счет приоритезации на уровне приложений и компенсации ошибок на каналах.
Резервное копирование — SD-WAN позволяет наладить максимально быструю и надежную транспортировку бэкапов по сети (с агрегацией каналов и обеспечением отказоустойчивости) по всем доступным каналам до облака.
S3 хранилище, облачный диск — при использовании сервисов блочного, объектного и файлового хранилищ подключение по SD-WAN позволяет передавать большие файлы на максимальных скоростях. Это достигается за счет отсутствия ретрансмитов при передаче данных и агрегирования каналов связи, подключенных к устройству SD-WAN.
GPU Super Cloud — высокоскоростная передача больших объемов данных для обработки в облачной среде.
Все чаще наши клиенты для подключения своих офисов к виртуальным дата-центрам «ИТ-ГРАД» и #CloudMTS вместо выделенных каналов выбирают SD-WAN и интернет-каналы. Клиенты, уже использующие выделенные каналы, применяют SD-WAN для агрегации существующих каналов, многократно повышая производительность и надежность подключения к облаку.
Будем рады пообщаться с вами в комментариях! В следующей статье мы проведем тестирование сервиса и на практике посмотрим, какие преимущества дают технология DMPO и протокол VCMP.
Большинство разработчиков, которые использовали рекурсию для решения своих задач, видели такую ошибку:
RangeError: Maximum call stack size exceeded.
Но не каждый разработчик задумывался о том, а что означает "размер стэка вызовов" и каков же этот размер? А в чем его измерять?
Думаю, те, кто работают с языками, напрямую работающими с памятью, смогут легко ответить на этот вопрос, а вот типичный фронтэнд разработчик скорее всего задает себе подобные вопросы впервые. Что-ж, попробуем разобраться!
Многие полагают, что браузер ограничивает нас именно в количестве вызовов, но это не так. В данной статье я покажу на простых примерах, как это работает на самом деле.
О чем ты вообще, автор?
Для статьи важно понимание таких понятий как Execution Stack, Execution Context. Если вы не знаете, что это такое, то советую об этом почитать. На данном ресурсе уже было достаточно хороших статей на эту тему. Пример - https://habr.com/ru/company/ruvds/blog/422089/
Когда возникает эта ошибка?
Разберем на простом примере - функция, которая рекурсивно вызывает сама себя.
const func = () => {
func();
}
Если попытаться вызвать такую функцию, то мы увидим в консоли/терминале ошибку, о которой я упомянул выше.
А что если подглядеть, сколько же раз выполнилась функция перед тем, как возникла ошибка?
На данном этапе код запускается в Chrome DevTools последней версии на март 2021. Результат будет различаться в разных браузерах. В дальнейшем в статье я упомяну об этом.
Для эксперимента будем использовать вот такой код:
let i = 0;
const func = () => {
i++;
func();
};
try {
func();
} catch (e) {
// Словили ошибку переполнения стэка и вывели значение счетчика в консоль
console.log(i);
}
Результатом вывода в консоль стало число в 13914. Делаем вывод, что перед тем, как переполнить стэк, наша функция вызвалась почти 14 тысяч раз.
Магия начинается тогда, когда мы начинаем играться с этим кодом. Допустим, изменим его вот таким образом:
let i = 0;
const func = () => {
let someVariable = i + 1;
i++;
func();
};
try {
func();
} catch (e) {
console.log(i);
}
Единственное, что мы добавили, это объявление переменной someVariable в теле функции. Казалось бы, ничего не поменялось, но число стало меньше. На этот раз функция выполнилась 12523 раз. Что более чем на тысячу меньше, чем в прошлом примере. Чтобы убедиться, что это не погрешность, пробуем выполнять такой код несколько раз, но видим одни и те же результаты в консоли.
Почему же так? Что изменилось? Как понять, посмотрев на функцию, сколько раз она может выполниться рекурсивно?!
Магия раскрыта
Отличие второго примера от первого - наличие дополнительной переменной внутри тела функции. На этом все. Соответственно, можно догадаться, именно из-за этого максимальное количество рекурсивных вызовов стало меньше. Что-ж, а что, если у нас будет не одна, а четыре переменных внутри функции? По этой логике, количество рекурсивных вызовов станет еще меньше? Проверим:
let i = 0;
const func = () => {
let a = i + 1;
let b = a + 1;
let c = b + 1;
let d = c + 1;
let e = d + 1;
i++;
func();
};
try {
func();
} catch (e) {
console.log(i);
}
По этой логике, значение счетчика должно стать еще меньше. Выдыхая, видим вывод - 8945. Ага, оно стало еще меньше. Значит, мы на правильном пути. Хочу привести небольшую аналогию, чтобы даже самым маленьким стало понятно.
Execution Stack (Call Stack) - это емкость с водой. Изначально она пустая. Так получилось, что эта емкость с водой стоит прямо на электрической проводке. Как только емкость переполнится, вода проливается на провода, и мы видим ошибку в консоли. При каждом новом рекурсивном вызове функции в стэк падает капелька воды. Само собой, чем капелька воды крупнее, тем быстрее наполнится стэк.
Емкости бывают разные по размеру. И размер емкости в данном примере - это размер коллстэка. А точнее - количество байт, которое он максимально может в себе удержать. Как гласит статья про Call Stack (Execution Stack), которую я приложил в начале, на каждый вызов функции создается Execution Context - контекст вызова функции (не путать с this). И упрощая, в нем, помимо разных "подкапотных" штук, содержатся все переменные, которые мы объявили внутри функции. Как Execution Context, так и каждая переменная внутри него имеют определенный размер, который они занимают в памяти. Сложив эти два размера мы и получим "размер" капли, которая капает в кувшин при каждом рекурсивном вызове функции.
У нас уже достаточно много данных. Может быть, поэкспериментируем еще? А давайте попробуем вычислить, какой размер коллстэка в движке, который использует Chrome?
Математика все-таки пригодилась
Как мы выяснили, у нас есть две неизвестные, которые составляют размер функции (капельки, которая падает в емкость). Это размер самого Execution Stack, а так же сумма размеров всех переменных внутри функции. Назовем первую N, а вторую K. Сам же неизвестный размер коллстэка обозначим как X.
В итоге - количество байт, которое занимает функция (в упрощенном виде) будет:
SizeOfVar в данном случае - количество байт, которые занимает переменная в памяти.
Учитывая, что мы знаем количество вызовов первой функции, в теле которой не объявляются переменные, размер коллстэка можно выразить как:
И, для второго случая, возьмем функцию, внутри которой было объявлено пять переменных.
Иксы в обоих случаях обозначают одно и то же число - размер коллстэка. Как учили в школе, можем приравнять правые части уравнений.
Выглядит неплохо. У нас тут две неизвестные переменные - N и SizeOfVar. Если N мы не можем откуда-то узнать, то что насчет SizeOfVar? Заметим, что во всех функциях, которые фигурировали выше, переменные хранили значение с типом "number", а значит, нужно просто узнать, сколько же байт в памяти занимает одна такая переменная.
С помощью великого гугла получаем ответ - "Числа в JavaScript представлены 64-битными значениями с плавающей запятой. В байте 8 бит, в результате каждое число занимает 64/8 = 8 байт." Вот она - последняя неизвестная. 8 байт. Подставляем ее в наше уравнение и считаем, чем равно N.
Упрощаем:
Если выразить отсюда N, то получим ответ: N равно приблизительно 72. В данном случае 72 байтам.
Теперь, подставив N = 72 в самое первое уравнение, получим, что размер коллстэка в Chrome равен... 1002128 байтов. Это почти один мегабайт. Не так уж и много, согласитесь.
Мы получили какое-то число, но как убедиться, что наши расчеты верны и число правильное? А давайте попробуем с помощью этого числа спрогнозировать, сколько раз сможет выполниться функция, внутри которой будет объявлено 7 переменных типа 'number'.
Считаем: Ага, каждая функция будет занимать (72 + 7 * 8) байт, это 128. Разделим 1002128 на 128 и получим число... 7829! Согласно нашим расчетам, такая функция сможет рекурсивно вызваться именно 7829 раз! Идем проверять это в реальном бою...
Мы были очень даже близки. Реальное число отличается от теоретического всего на 3. Я считаю, что это успех. В наших расчетах было несколько округлений, поэтому результат мы посчитали не идеально, но, очень-очень близко. Небольшая погрешность в таком деле - нормально.
Получается, что мы посчитали все верно и можем утверждать, что размер пустого ExecutionStack в Chrome равен 72 байтам, а размер коллстэка - чуть меньше одного мегабайта.
Отличная работа!
Важное примечание
Размер стэка разнится от браузера к браузеру. Возьмем простейшую функцию из начала статьи. Выполнив ее в Сафари получим совершенно другую цифру. Целых 45606 вызовов. Функция с пятью переменными внутри выполнилась бы 39905 раз. В NodeJS числа очень близки к Chrome по понятным причинам. Любопытный читатель может проверить это самостоятельно на своем любимом движке JavaScript.
А что с непримитивами?
Если с числами все вроде бы понятно, то что насчет типа данных Object?
let i = 0;
const func = () => {
const a = {
key: i + 1,
};
i++;
func();
};
try {
func();
} catch (e) {
console.log(i);
}
Простейший пример на ваших глазах. Такая функция сможет рекурсивно вызваться 12516. Это практически столько же, сколько и функция с одной переменной внутри. Тут в дело вступает механизм хранения и передачи объектов в JS'е - по ссылке. Думаю, большинство уже знают об этом.
А что с этим? А как поведет себя вот это? А что с *?
Как вы заметили, экспериментировать можно бесконечно. Можно придумать огромное количество кейсов, которые будут прояснять эту ситуацию глубже и глубже. Можно ставить эксперименты при разных условиях, в разных браузерах, на разных движках. Оставляю это на тех, кого эта тема заинтересовала.
Итоги:
Количество рекурсивных вызовов функции до переполнения стэка зависит от самих функций.
Размер стэка измеряется в байтах.
Чем "тяжелее" функция, тем меньше раз она может быть вызвана рекурсивно.
Размер стэка в разных движках различается.
Вопрос особо любознательным: А сколько переменных типа "number" должно быть объявлено в функции, чтобы она могла выполниться рекурсивно всего два раза, после чего стэк переполнится?
Некоторое время назад я занимался одной интересной задачей, относящейся к спутниковой навигации. Используя фазовый фронт сигнала, объект навигации (ОНВ) измеряет координаты навигационных спутников (НС) в своей системе координат (локальная система, ЛСК). Также ОНВ получает значения положений НС в глобальной системе координат (ГСК), и измеряет время получения сигнала НС (рис. 1). Требовалось вычислить координаты ОНВ в ГСК и системное время, то есть решить навигационную задачу.
Задача была интересна тем, что её решение теоретически позволяет уменьшить число НС в сравнении с тем, сколько НС требуется в методах, реализованных в спутниковых системах навигации. Своё внимание в то время я в основном уделял исследованию качества измерений фазового фронта и получению навигационных уравнений для координат и времени, полагая при этом, что вычисление ориентации и координат ОНВ не вызовет особых проблем. Тем более, что на плоскости задача решалась быстро и просто.
Однако, когда я построил модель в трёхмерном пространстве, неожиданно выяснилось, что вычислить значения ориентации ОНВ при неизвестных его координатах в ГСК не получается. Несколько предпринятых попыток определить ориентацию с помощью матриц направляющих косинусов и поворотов привели к такому нагромождению тригонометрических функций, что продвигаться дальше к решению у меня не получалось. Какое-то время даже казалось, что аналитического решения вообще не существует.
Но оно, конечно, существует. Мне удалось найти решение этой задачи, используя свойства кватернионов. В этом материале я хочу описать саму задачу, ход и её решение, уделяя внимание ориентации и координатам ОНВ, и пока оставляя за рамками измерения координат по фазовому фронту.
Входные данные
Итак, входные данные:
: вектор-столбец положения -го НС в ГСК, : номер НС,
: вектор-столбец положения -го НС в координатах ЛСК; векторы и полагаем известными; ГСК, ЛСК: правые декартовы системы координат в 3-х мерном евклидовом пространстве с разнонаправленными базисами и соответственно; начала координат ГСК и ЛСК не совпадают. Нижний индекс дальше будет обозначать номер вектора, верхний - номер элемента в векторе, если только не указано явно, что это степень.
Задача
Нужно найти:
: вектор-столбец положения ОНВ в ГСК,
: оператор перехода от ЛСК к ГСК, который я условно назвал "оператором ориентации" (рис. 2)
Решение
Теперь ход моих рассуждений и решения.
Разложение векторов и в своих базисах: , где - номер НС. Базисные векторы ЛСК
где , - множество вещественных чисел, . Выписывая эти коэффициенты в матрицу
получаем
полагая, что , , . Следовательно, координаты вектора в базисе ГСК . Можно сказать, что является матрицей некоторого линейного оператора (или "оператора ориентации"), такого, что , определённого в базисе ГСК. Такой матрицей может быть матрица направляющих косинусов или любая из матриц поворотов.
Свойства произвольного евклидового пространства позволяют записать уравнение для вычисления вектора положения ОНВ:
(1)
Неудачный поиск решения
Уравнение (1) содержит две неизвестные матричные величины и , и имеет поэтому бесконечное число решений. Аналогичное соотношение для трёх различных НС в виде
где
квадратные невырожденные матрицы, также содержат две неизвестные матричные величины и . Можно переписать (1) и (2) так, чтобы избавиться от величины :
откуда
или
где .
Если бы я смог как-нибудь найти из (3) и подставить в (1), то задача была бы решена. К примеру, была сделана попытка расписать (3) по трём НС аналогично с (2), но в итоге матрицы получались вырожденные и уравнение единственного решения поэтому не имело. Попытки расписать и решить систему уравнение вроде
приводили к тому самому нагромождению синусов и косинусов, о котором я упомянул во вступлении.
Здесь я и подумал, а получится ли найти , если (3) или (4) записать в кватернионном виде. В итоге получилось, но продолжу по порядку.
Теория о кватернионе поворота
Два теоретических момента, которые, думаю, стоит упомянуть.
Кватернионом, как мы знаем, является математический объект вида , где , , , , - скалярная часть (множитель вещественной единицы), - векторная часть; 1, i, j, k - вещественная и три разные мнимые единицы с таблицей умножения:
Кватернион даёт удобную возможность представления трёхмерных преобразований (вращений), определяя одновременно и ось поворота, и угол вращения. Если взять некоторый кватернион , такой, что , то можно записать, что , и значит , где , . Здесь индекс в скобках обозначает номер элемента, а верхний индекс без скобки - возведение в степень. Если вектор представить как
где , , , то кватернион запишется так:
где . Если теперь взять произвольный кватернион с нулевой скалярной частью и вектором , то результатом операции
будет вектор с той же длиной, что и , но повёрнутый на угол против часовой стрелки вокруг оси, направляющим вектором которой является . Дальше будут встречаться фразы вроде "кватернион выполняет поворот вектора ", которые, конечно, подразумевают применение кватерниона к вектору и получение в соответствии с (5).
Последний теоретический момент. Для обозначения разных видов умножения используются такие общеизвестные значки: или : скалярное произведение,: кватернионное произведение,: векторное произведение.
На этом с теорией всё.
Описание решения с кватернионами
Вернёмся к векторам и . Три замечания об их характере, которые потребуются дальше. Примем пока k = 1 , i = 2.
Нужные замечания
Во-первых, эти векторы являются свободными векторами, которые можно перемещать в пространстве, соблюдая параллельность перемещений.
Во-вторых, они неколлинеарны. Действительно, если бы они были коллинеарными, то (3) обращалось бы в истинное высказывание только при единичной матрице и задачу решать не нужно было.
И, в-третьих, . В самом деле, так как - это ортогональная матрица, которая не меняет длину вектора , то из (3) следует, что длины векторов и равны.
Tак как в (3) длины векторов не имеют значения, для упрощения записей и решения дальше заменю векторы и их нормированными эквивалентами:
и в виде кватернионов:
Кватернионная форма основных уравнений
Выражение (3) в кватернионной форме выглядит теперь так:
где - неизвестный кватернион, эквивалент , а выражение (1) так:
где , .
Так как векторы и свободны, неколлинеарны, то можно совместить их концы таким образом, чтобы и образовали угол на плоскости, натянутой на эти векторы (пусть эта плоскость будет ). Начало координат поместим в точку, общую для и (рис. 3), и будем полагать, что значение известно.
Уравнение (6), аналогично уравнению (3), по-прежнему имеет бесконечное множество решений: можно найти сколько угодно различных , которые удовлетворяют (6). Геометрически это обозначает, что можно найти бесконечное число различных прямых, вокруг которых можно выполнить вращение, совмещающее с . На рис. 3 приведён пример, в котором поворот выполняется по кратчайшему пути вокруг прямой с направляющим вектором , ортогональным плоскости . Очевидно, что .
На рис. 4, а) и б) приведён пример, в котором вокруг некоторой оси с направляющим вектором построен круговой конус с направляющими прямыми и . Ясно, что вокруг такой оси можно сделать поворот, совмещающий с , который будет выполнен не по кратчайшему пути (не в плоскости ), и угол , измеряемый в плоскости основания конуса , не будет равен , измеряемый в плоскости .
Чтобы найти неизвестное значение , я добавил два новых объекта и , получаемые из векторов и . Выражение (6), аналогично (4), расширяется до системы уравнений
Теперь можно утверждать, что кватернион , найденный из этой системы уравнений (8), является единственным решением, и он выполняет такой поворот в пространстве, который совмещает тройку векторов базиса ЛСК с векторами ГСК. Следовательно, его можно подставить в выражение (7) и вычислить искомое - положение ОНВ.
Когда я записал (8), отчего-то стало ясно, что здесь безразмерная куча тригонометрии может не возникнуть, и аналитическое решение поэтому можно будет найти более-менее просто.
Геометрия задачи
Геометрически задача теперь выглядит так. Нужно найти в пространстве такую ось, вокруг которой можно выполнить поворот, совмещающий вектор с вектором , и вектор с вектором . Прямые, направляющими векторами которых являются (или ) и (или ), образуют два различных круговых конуса , . Эти конусы имеют общую ось, направляющим вектором которой является искомый . При этом повороты к , i = 1, 2, будут выполняться не по кратчайшему пути, и поэтому углы , измеренные в плоскости оснований конусов , будут отличаться от углов (рис. 5).
Здесь и далее нам понадобятся два таких утверждения.
Утверждение 1. Угол поворота вокруг биссектрисы такого, который совмещает с , равен (рис. 4, в), г)).
Утверждение 2. Поворот, который совмещает с , i = 1, 2, вокруг некоторой прямой можно сделать тогда и только тогда, когда эта прямая лежит в плоскости, натянутой на и (плоскость ), рис. 6). Вокруг любой другой прямой такой поворот выполнить нельзя.
Для доказательства этих утверждений нужно рассмотреть свойства кругового конуса , образованного линией, направляющий вектор которой равен (или ), а ось лежит в плоскости .
Очевидно, что ось, вокруг которой можно сделать такой поворот, который совместит с , i = 1, 2, будет одновременно принадлежать обеим плоскостям , то есть будет совпадать с линией их пересечения. Поэтому вектор будем искать из уравнения линии пересечения плоскостей (рис. 7).
Эта прямая не будет совпадать с прямыми, определяемые направляющими векторами . Проходя через начало координат, она образует некоторый угол с вектором , и угол с вектором (рис. 8). Оба этих угла нам пока неизвестны и .
Посмотрим на рис. 8. Если взять некоторый кватернион , который поворачивает вектор на в плоскости , т.е.
и повернуть его вокруг начала координат на угол в плоскости , то из
мы получим вектор , который в свою очередь является кватернионом поворота на угол для вектора , причём при этом будет описывать дугу в пространстве. Поворот к может быть выполнен кватернионом , который мы найдём чуть позже из условия его ортогональности к плоскости .
Из утверждения 2 следует, что вектор может быть совмещён с вращением вокруг оси с направляющим вектором на некоторый угол , который, очевидно, функционально зависит от . Поэтому, зная и зависимость , мы сможем построить из кватерниона кватернион , являющийся решением задачи.
Зависимость мы найдём немного позже из простых тригонометрических соотношений.
Угол будем искать из следующих соображений. Так как ортогонален одновременно и , то результат векторного произведения будет сонаправлен с . Обозначим
и запишем такое скалярное произведение: . Отметим, что направление не зависит ни от , ни от . Поэтому для вычисления примем и , то есть угол, при котором . Следовательно, в записанном выше скалярном произведении остаётся одна переменная , которую можно вычислить, решив уравнение
полагая, что . Теперь, зная , зависимость и вектор , искомый кватернион будет выглядеть так:
Осталось найти каждый из описанных выше элементов, чтобы решить задачу. Заметим, что объекты , , , , , определяются аналогично. Поэтому далее нижний индекс "1" или "2" буду заменять на " i ", подразумевая, что i = 1, 2 .
Кватернионы, которые будем искать
Ещё раз перечислим кватернионы и векторы, которые мы сейчас будем строить для получения решения:
кватернион : совмещает вектор с по кратчайшей траектории,
кватернион : направляющий вектор биссектрисы угла между векторами и ; векторы и определяют плоскость , в которой лежит искомый кватернион ,
кватернион : сонаправлен с ; модуль векторной части равен 1,
кватернион : поворачивает вектор в плоскости на угол ; значение неизвестно,
зависимость : вычисляет для построения искомого кватерниона из ,
кватернион : получен поворотом вектора в плоскости на угол ; из будет получено решение,
кватернион : нужен для вычисления угла ,
и, наконец, результирующий кватернион : получается из , учитывая найденные и .
Кватернион
Найдём кватернион , который совмещает с по кратчайшей траектории. Вектор перемещается в плоскости , а ось поворота перпендикулярна и проходит через точку начала координат (рис. 3). Направляющий вектор этой оси может быть равен . Так как , и , то:
где - единичный вектор, равный
где
Произведение может представлять собой векторную часть некоторого кватерниона , который выполняет поворот вектора на угол в плоскости : . Поэтому кватернион поворота на угол равен
и
Кватернион
Найдём кватернион . Он может быть получен из преобразования
где - кватернион, выполняющий поворот вектора на угол . Учитывая (17), он равен
Подставим из (19) в (18), выполним кватернионные умножения и, учитывая (14), получим:
Применяя правило "БАЦ минус ЦАБ" и упрощая, получаем
Заметим, что .
Зависимость
Найдём зависимость . Возьмём конус из рис. 6, для удобства развернём его основанием вниз и изобразим все основные векторы и углы (рис. 9). Также добавим угол .
Будем находить зависимость угла , соответствующего углу поворота вокруг от к , от значения угла , то есть от угла наклона к плоскости векторов , (т.е. к плоскости ). Заметим, что при поворот выполняется по кратчайшей траектории вокруг .
Из соотношений прямоугольных треугольников запишем:
откуда . Так как ), то
откуда
Из выражения (21) видно, что при значение , а при значение , что согласуется с утверждением 1. Заметим также, что угол не зависит от длин векторов, а угол полагается известным.
Кватернион
Продолжим. Построим кватернион . Поскольку он выполняет поворот на угол в плоскости , то
полагая, что , . Учитывая (9), (18), выполнив некоторые преобразования, получим
После упрощающих тригонометрических преобразований, подставляя в (22), получаем
при этом .
Кватернион
Найдём кватернион . Выше мы записали выражение (9), которое определяет . Приведём его здесь ещё раз: . Подставляя сюда полученный результат (23) и выражение (9), после преобразований получим:
где , , . Упрощая, получаем:
при этом .
Кватернион
Получим кватернион . Как мы указывали выше, вектор этого кватерниона ортогонален каждому из векторов и , то есть (выражение (11)), и при этом . Также мы отмечали, что угол не меняет направление вектора , и поэтому можно выбрать такое , при котором длина максимальна. Учитывая (23) при , получаем:
следовательно,
Нормированное значение
Угол
Подставляем результаты в выражение (12) и получаем уравнение, которое нужно решить относительно :
После преобразований получаем это уравнение в форме , которое и решаем:
где
Решение
Зная , можно построить кватернион из (24), подставить его в (13) и записать результат в виде:
где
а вычисляется из выражения (29).
Послесловие
Задача решена. Я попытался упростить итоговое выражение (30), но пока не получилось. В моделях оно работает, поэтому пока оставлю, как есть. На рис. 10 приведён простой пример нахождения ориентации ЛСК для того, чтобы затем определить .
Вначале положение ЛСК неизвестно, и мы полагаем, что оно может быть любым или, например, совпадающим с ГСК (то есть, мы "думаем", что оно совпадает, а на самом деле это не так). Измеренные координаты НС1', НС2' и НС3' существенно отличаются от истинных положений НС1, НС2 и НС3 (рис. 10, а)). Зная координаты НС в ГСК, вычисляется кватернион поворота по (30) и выполняется вращение. На рис. 10, б) показано дискретное вращение с интервалом 10 градусов от старого положения ЛСК к истинному. При этом измеренные координаты НС (показаны светло-серым цветом) всё более приближаются к истинным. На рис. 10, в) показано вычисленное истинное положение ЛСК, и мы теперь можем определить , то есть решить навигационную задачу (точнее, часть навигационной задачи, связанной с определением координат).
На этом пока всё. Отмечу только, что в ближайшем будущем я попробую поработать над одним недостатком выражения (30). При близким к нулю, то есть когда ориентации ГСК и ЛСК мало отличаются, кватернион вычисляется с ошибкой из-за множителя . Это может приводить к значительным ошибкам вычисления ориентации ЛСК и, как результат, ошибкам определения положения . Об этом в следующем материале.