LINUX.ORG.RU

А куда, если не в веб?

 , ,


3

4

Хай, Лор. Давно сюда не писал, многое поменялось, многое перепробовал.

Но вот незадача - присутствует чувство, что всё идёт сейчас в веб. Я уже было смирился с этой мыслью, но тут начинается…Чертов фронтенд. Вот не нравится мне размечать документы и объяснять машине по полчаса, почему и какая кнопка, где должна находиться.

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

Подскажите, есть ли истории успеха? Что-то алгоритмическое/техническое? Буду рад всем отписавшимся по вопросу.

Ответ на: комментарий от byko3y

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

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

alysnix ★★★
()
Ответ на: комментарий от byko3y

Я писал о том, что влияние volatile на производительность ничтожно, а не о том, где ее нужно и где не нужно писать. Какая разница где нужно писать то, что не имеет значения?

ответьте на вопрос. влияние оптимизации на производительность ничтожно или нет? volatile просто позволяет проводить КОРРЕКТНУЮ максимально глубокую оптимизацию. считаете ли лично вы это ничтожным занятием или нет, это не столь важно. возьмите код без оптимизации вообще и с оптимизацей и сравните тайминги.

alysnix ★★★
()
Ответ на: комментарий от byko3y

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

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

anonymous
()
Ответ на: комментарий от byko3y

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

Если переменную, экспортированную с volatile, использовать в другом потоке, то тоже будет лажа, так как volatile и атомарность - разные вещи, и не знать этого позволительно только диванному комментатору, а никак не программисту.

annulen ★★★★★
()
Ответ на: комментарий от alysnix

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

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

pihter ★★★★★
()
Ответ на: комментарий от seiken

Я пока не увидел ни одного аргумента в пользу Лазаря

а я не хотел именно за Лазаря: я хотел утвердить, что тот самый «интерфейс из 90-х» который предоставляет Лазарь – не уродский, а вполне адекватный.

Нафиг Лазарь сдался практикующему плюсисту - не понятно.

Ну я такого и не утверждал, что сдался

pihter ★★★★★
()
Ответ на: комментарий от zendrz

Не хочу париться с управлением памятью и прочим В Pascal с этим проще

Без RAII то куда уж проще, ага

Ты о чем вообще? Ты хочешь сказать, что в паскале нет RAII? Я тебя удивлю: на строках, динамических массивах, и интерфейсах есть многопоточный подсчет ссылок, а на Variant и прочие структуры, которые содержат динамически выделенную память (кроме class/object) автоматически высвобождаются при помощи генерации блока try..finally вокруг области видимости. Подсчет ссылок тоже теми же блоками try..finally делаются вокруг области видимости переменной.

byko3y ★★★★
()
Ответ на: комментарий от alysnix

Экспортировали функцию,что просто инкрементировала эту скрытую переменную, причем даже неявно, а через цепь вызовов

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

byko3y ★★★★
()
Ответ на: комментарий от alysnix

volatile просто позволяет проводить КОРРЕКТНУЮ максимально глубокую оптимизацию

Да, оно корректным становится с volatile. Некорректными были оптимизации, из-за которых вводили volatile — причина и следствие здесь важны.

возьмите код без оптимизации вообще и с оптимизацей и сравните тайминги

При чем тут это? Я пишу конкретно про volatile. Это, мягко говоря, не все возможные оптимизации компилятора.

byko3y ★★★★
()
Ответ на: комментарий от anonymous

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

И? Что тебе есть возразить конкретно? Можно на смартфонах выполняться, можно быть воздушным асом, можно стать воздушной планетой. Почему-то до сих пор никто не задал вопрос «если в паскале так много семантики volatile, то может ли он работать быстро в сборке с оптимизацией?».

byko3y ★★★★
()
Ответ на: комментарий от annulen

Если переменную, экспортированную с volatile, использовать в другом потоке, то тоже будет лажа, так как volatile и атомарность - разные вещи, и не знать этого позволительно только диванному комментатору, а никак не программисту.

программист также должен знать что операции чтения базовых типов и приваивания, изменения, обычно атомарны на уровне архитектуры проца. то есть обобщенная команда mov reg, mem - реализована в проце как АТОМАРНАЯ. Она не может быть прервана посредине, и если началась исполняться, процессор не переключит исполнение, пока она не закончится. таким образом - это ваше утверждение неверно.

а вот это верно: примитивные операции со скалярными типами(то есть любая переменная что может уместиться в регистре проца)(int, bool, pointer, reference,…), атомарны изначально. если операция укладывается в одну инструкцию процессора - она атомарна. потому тут достаточно написать, что такая переменная волатильна, и не заботиться о синхронизации или атомарности.

НО! надо понимать, что операция типа ++x(где x: integer), не атомарна в общем случае(если у проца нет инструкции актомарного инкремента/декремента). поскольку тут надо сначала надо загрузить x, потом инкрементировать, потом запомнить в памяти. то есть если x объявлена как volatile, то присвоение и чтение атомарны, а вот инкремент/декремент, или блок чтение-изменение-присвоение - нет.

alysnix ★★★
()
Ответ на: комментарий от alysnix

При чем тут это? Я пишу конкретно про volatile. Это, мягко говоря, не все возможные оптимизации компилятора.

опять… volatile - не оптимизация. это управление оптимизацией, а проще говоря - ее частичный запрет для переменных таким образом объявленных. еще раз повторяю. такой спецификатор необходим!, для того чтобы оптимизация с размещением переменных в регистрах, была корректной для случаев паралелльных активностей.

alysnix ★★★
()
Ответ на: комментарий от fsb4000

Overview: A volatile intrinsic has been added to indicate to the code generator that a particular load from or store to a memory location must not be removed
Example: https://svn.freepascal.org/svn/fpc/trunk/tests/test/tmt1.pp

Детишки попросили цацу — им дали цацу. Чем бы дитя не тешилось — лишь бы не плакало. Приведенный в примере код реализует семантику volatile без каких-либо дополнительных действий. Я сейчас скомпилировал вырезки из этого кода:

...

var
   finished : longint;
threadvar
   thri : cardinal;

procedure f(p : pointer);
begin
   InterLockedIncrement(finished);
end;

var
  started: longint;
begin
  CreateThread(nil, 0, @f, nil, 0, thri);
  while finished<started do
     sleep(10);
  writeln(finished);
end.

А вот дизасм самых интересных кусков в релизной сборке:

f:
	mov eax,$00000001
	lock xadd [$005e6198],eax
	add eax,$01
	ret 

main:
	...
	mov esi,$005e6198
	...
	call CreateThread
	inc ebx
	cmp ebx,[esi]
	jle $005d957e
	push $0a
	call Sleep
	cmp ebx,[esi]
	jnle $005d9573
	mov eax,[$005deb7c]
	mov edx,[esi]
	call @Write0Long
	call @WriteLn
	call @_IOTest

Как видите, volatile никакого явного нет, но finished получила семантику volatile, а started-- нет, и попала в регистр ebx.

byko3y ★★★★
()
Ответ на: комментарий от annulen

Если переменную, экспортированную с volatile, использовать в другом потоке, то тоже будет лажа, так как volatile и атомарность - разные вещи, и не знать этого позволительно только диванному комментатору, а никак не программисту

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

byko3y ★★★★
()
Ответ на: комментарий от byko3y

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

То есть это даже не ошибка, а осознанное вредительство. Ок.

annulen ★★★★★
()
Ответ на: комментарий от alysnix

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

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

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

byko3y ★★★★
()
Ответ на: комментарий от byko3y

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

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

mov reg, field
//тут всяко работаем с reg как c field
mov field, reg. //а тут восставнливаем field

Но такой пседокод НЕЛЕГАЛЕН, если field может меняться паралельно. то есть volatile для поля класса - это совершенно нормальная и НЕОБХОДИМАЯ конструкция.

alysnix ★★★
()
Ответ на: комментарий от annulen

То есть это даже не ошибка, а осознанное вредительство. Ок

В MSVC по этой причине у volatile добавлена семантика атомарного доступа. Это сделано, чтобы можно было переносить x86 код на ARM, который недавно стал поддерживаться MSVC.

byko3y ★★★★
()
Последнее исправление: byko3y (всего исправлений: 1)
Ответ на: комментарий от alysnix

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

mov reg, field
//тут всяко работаем с reg как c field
mov field, reg. //а тут восставнливаем field

Но такой пседокод НЕЛЕГАЛЕН, если field может меняться паралельно. то есть volatile для поля класса - это совершенно нормальная и НЕОБХОДИМАЯ конструкция

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

byko3y ★★★★
()
Ответ на: комментарий от alysnix

а вот это верно: примитивные операции со скалярными типами(то есть любая переменная что может уместиться в регистре проца)(int, bool, pointer, reference,…), атомарны изначально.

Нет, неверно, стандарты С и С++ таких гарантий не дают. В C++, например, есть std::atomic_flag, который гарантированно не использует внутренних блокировок, и типы std::atomic, которые атомарны, но могут иметь внутренние блокировки.

annulen ★★★★★
()
Ответ на: комментарий от byko3y

Детишки попросили цацу — им дали цацу. Чем бы дитя не тешилось — лишь бы не плакало

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

https://wiki.freepascal.org/FPC_New_Features_Trunk

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

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

Это уже сишник головного мозга. Как Торвальдс, который так и не смог выучить ни одного нового языка.

byko3y ★★★★
()
Ответ на: комментарий от byko3y

MSVC — это сам себе стандарт, на правах монополиста рынка.

Какого именно рынка? Черкизовского? Да и я смотрю вы тут С обсуждаете, а компилятора С у них до сих пор нет

annulen ★★★★★
()
Ответ на: комментарий от byko3y

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

будет. взятие на регистры переменных - это размещение на регистрах ЛЮБЫХ переменных, где бы они не находились физически, и максимально долгая с ними работа. а потом восстановление их значений из регистра. такая оптимизация решает вопрос - насколько корректно создать копию ячейки памяти в регистре, работать с этой копией и потом восстановить память. по сути регистр тут - кеш для ячейки памяти. а расположение локальных переменных и параметров функции сразу на регистрах - это лишь частный случай этой оптимизации.

alysnix ★★★
()
Ответ на: комментарий от alysnix

а вот это верно: примитивные операции со скалярными типами(то есть любая переменная что может уместиться в регистре проца)(int, bool, pointer, reference,…), атомарны изначально. если операция укладывается в одну инструкцию процессора - она атомарна. потому тут достаточно написать, что такая переменная волатильна, и не заботиться о синхронизации или атомарности.

Типичный пример: на архитектурах вроде MIPS и PPC длина команды 32 бита, поэтому 32-битный immediate operand ты за одну инструкцию ну никак в регистр не положишь, поэтому кладут двумя инструкциями по половинке

annulen ★★★★★
()
Ответ на: комментарий от alysnix

будет. взятие на регистры переменных - это размещение на регистрах ЛЮБЫХ переменных, где бы они не находились физически, и максимально долгая с ними работа. а потом восстановление их значений из регистра. такая оптимизация решает вопрос - насколько корректно создать копию ячейки памяти в регистре, работать с этой копией и потом восстановить память. по сути регистр тут - кеш для ячейки памяти. а расположение локальных переменных и параметров функции сразу на регистрах - это лишь частный случай этой оптимизации

Хорошо, «будет» где? В C/C++? Не сомневаюсь. В паскале? Нет. Копии создаются только явно. Конкретно в Delphi недавно сделали агрессивную оптимизацию для private/protected полей и добавили атрибут "[volatile]" для них же, хотя я бы на их месте сделал обратный атрибут для позволения агрессивных оптимизаций на отдельных интенсивно используемых полях. Но они — не я, и они стремительно скатываются на дно, поскольку ставят низкоквалифицированных индусов на разработку, которые, в частности, поломали Align/Anchors в VCL, и написали тормознутый и глюкавейшый x64 отладчик, который заметно контрастирует со старым 32-битным.

byko3y ★★★★
()
Ответ на: комментарий от annulen

Какого именно рынка? Черкизовского? Да и я смотрю вы тут С обсуждаете, а компилятора С у них до сих пор нет

Рынка десктопов. Сколько там у MS было доли в 90-00-е? 95%? Даже в 2008 году эта цифра была 90%.

byko3y ★★★★
()
Ответ на: комментарий от annulen

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

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

alysnix ★★★
()
Ответ на: комментарий от byko3y

Рынка десктопов

Уже не актуально, сейчас большая часть С++-рныка это сервера, эмбедщина и мобилки.

annulen ★★★★★
()
Ответ на: комментарий от byko3y

Хорошо, «будет» где? В C/C++? Не сомневаюсь. В паскале? Нет.

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

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

alysnix ★★★
()
Ответ на: комментарий от annulen

Ты похоже невнимательно читаешь. Там нужны ДВЕ команды процессора.

да бог с ними, даже обсуждать не хочу. архитектуры в этом мире люди придумывают, и они могут придумать любую блажь. в нормально разработанном проце команды атомарны, как раз чтобы не морочить себе потом голову. переключение исполнения может быть только между командами, но не внутри. это НЕ КАСАЕТСЯ специфических циклических команд что есть например у интела типа cmps, movs и прочее. поскольку это не атомарная команда.

alysnix ★★★
()
Ответ на: комментарий от alysnix

в нормально разработанном проце

Но это точно не про x86

annulen ★★★★★
()
Ответ на: комментарий от alysnix

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

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

annulen ★★★★★
()
Ответ на: комментарий от alysnix

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

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

byko3y ★★★★
()
Ответ на: комментарий от byko3y

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

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

alysnix ★★★
()

сходи подрочи, набей руку так сказать

anonymous
()
Ответ на: комментарий от alysnix

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

annulen ★★★★★
()
Ответ на: комментарий от byko3y

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

никакой язык не может знать изменится переменная или нет например в memory mapped io без явного указания - не надо тут блевотину на уши вешать про могущество поцкаля.

anonymous
()
Ответ на: комментарий от annulen

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

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

alysnix ★★★
()
Ответ на: комментарий от annulen

Бывает такое, да, поэтому лучшие бенчи основаны на реальном коде, а не синтетике

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

alysnix ★★★
()
Ответ на: комментарий от anonymous

никакой язык не может знать изменится переменная или нет например в memory mapped io без явного указания - не надо тут блевотину на уши вешать про могущество поцкаля

Так, хорошо, у нас есть read-only MM IO. Как ты собираешь его оптимизировать? Естественно, несколько мегабайта ни в какие регистры не влезут - придется читать из кэша и оперативной памяти. Чтобы была возможность оптимизации через регистры, нужно вычленить какой-то ограниченный набор ячеек, который используется часто, например, какой-нибудь count, size, index, и держать их в регистрах, пока мы перелопачиваем мегабайты массивов. Речь идет про что-то такое:

for (int i = 0; i < data->count; ++i)
    process_item(&data->items[i]);
Правда, здесь я решительно не понимаю, почему нельзя сделать явно:
int count = data->count;
for (int i = 0; i < count; ++i)
    process_item(&data->items[i]);
Всё, ты сделал оптимизацию в рамках голого Си, которая будет работать даже при полностью выключенной оптимизации компилятора. И ради автоматизации этого нужно было засовывать в компилятор оптимизацию, которая создает ошибки работы программы, причем, эти ошибки никогда не проявляются с отключенной оптимизацией? Я не вижу в ней плюсов — я вижу только минусы.

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Правда, здесь я решительно не понимаю, почему нельзя сделать явно:

int count = data->count;
for (int i = 0; i < count; ++i)
    process_item(&data->items[i]);

вы «явно» тут мало что сделали, даже для такого примитивного кода. тут есть еще пара способов ускорить код. и оптимизатор их знает.
а для кода наворочанного, практически ТОЛЬКО оптимизатор знает, как его улучшить. потому что он в курсе архитектурных особенностей целевой платформы, а вы - нет

alysnix ★★★
()
Ответ на: комментарий от alysnix

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

Каких, и какое отношение они имеют в разговору?

для кода наворочанного, практически ТОЛЬКО оптимизатор знает, как его улучшить. потому что он в курсе архитектурных особенностей целевой платформы, а вы - нет

«Навороченный код» с глубоким использованием платформы пишется только на асме.

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Каких, и какое отношение они имеют в разговору?

я даже 5 доп методов оптимизации(кроме вашего) этого кода насчитал… а вот какие они - попробуйте ответить сами.

а какое отношение это имеет? вы же сами сказали, что оптимизация ненужна, потому вы вынесли выражение из цикла и на этом оптимизация закончилась. а она вовсе не закончилась. вот и ищите сами.

alysnix ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.