Помощь - Поиск - Пользователи - Календарь
Полная версия: Работа с таймером.
Форум «Всё о Паскале» > Pascal, Object Pascal > Теоретические вопросы
Shub
И так такт таймера по умолчанию составляет 55 мс. Но в жизни есть такие чудесные моменты, когда требуется, что-нибудь периодически выполнять через 20 мс, 10 мс или даже 1 мс. В сети выложены примеры быстрых таймеров, но, честно говоря, что к чему в них я так понять и не смог. Из не проверенных данных я знаю, что изменить такт таймера с 55 мс на 10 мс условно говоря просто, вот только ни где не сказано как это сделать. Как получают более мелкие такты для меня большая загадка.
Просветите, пожалуйста.

Примеры перепрограммирования частот системного таймера находятся здесь:
http://pascal.sources.ru/datetime/index.htm

PS Я не программист, так балуюсь, С и Асм’у не обучен.
Altair
А здесь можно узнать, как программировать таймер с высокой частотой. (с примерами)
http://forum.sources.ru/index.php?showtopic=40173
Shub
Спасибо, хоть там и много асма, но сообщения Vesper'a достаточно интересные особенно если к ним добавить данные из вот этой ссылки:
http://www.dklab.ru/doc/timer.html
Altair
Кстати очень интересную ссылку подкинул Shub, замер времени - очень интересная вещь!
Советую всем посмотреть этот сайт.
Shub
Попробовал то, что предложил Vesper (имеется в виду паскалевский вариант) с измененной частотой таймера как в моей последней ссылке - фокус не удался, время вычисляется 1 раз после чего наступает полный абзац требующий отлючения питания. unsure.gif
Altair
Хм .... Надо попробовать самому.
Я тоже интерисуюсь замером времени
Altair
Так, так ....
читаем, что тут:
http://www.dklab.ru/doc/timer.html
написанно:
Цитата
BIOS устанавливает это граничное значение в 0, что для таймера обозначает 65536. То есть каждый 65536-й "тик" часов генерируется прерывание 8, оно "срабатывает" примерно 119318/65536=18.2 раз в секунду.

А теперь берем листок, ручку и в столбик делим 119318 на 65536 - получаем на разряд меньше.
Где ложь?
----
Я пробовал запускать прогу оттуда, - НИЧЕГО!
Часы как шли так и идут.
---
А почему так:
Код

Port[$43]:=6;
Port[$40]:=Lo(cnt);     { младший байт значения }
Port[$40]:=Hi(cnt)      { а затем старший байт }

а не так
Код

Port[$43]:=6;
PortW[$40]:=cnt;

Не ясно...
Shub
Так добавлю еще одну ссылку:
http://www.vcl.ru/html/dos/appar/ch5.htm

Важные выдержки оттуда:
Цитата
Кроме часов реального времени, любой компьютер (даже простейший IBM PC) содержит устройство, называемое системным таймером. Это устройство подключено к линии запроса на прерывание IRQ0 и вырабатывает прерывание INT 8h приблизительно 18,2 раза в секунду (точное значение - 1193180/65536 раз в секунду).

При инициализации BIOS устанавливает свой обработчик для прерывания таймера. Этот обработчик каждый раз увеличивает на 1 текущее значение четырехбайтовой переменной, располагающейся в области данных BIOS по адресу 0000:046Ch - счетчик тиков таймера. Если этот счетчик переполняется (прошло более 24 часов с момента запуска таймера), в ячейку 0000:0470h заносится 1.

Другое действие, выполняемое стандартным обработчиком прерывания таймера - контроль за работой двигателей НГМД. Если после последнего обращения к НГМД прошло более 2 секунд, обработчик прерывания выключает двигатель. Ячейка с адресом 0000:0440h содержит время, оставшееся до выключения двигателя. Это время постоянно уменьшается обработчиком прерывания таймера. Когда оно становится равно 0, обработчик выключает двигатель НГМД.


Цитата
Таймеру соответствуют четыре порта ввода/вывода со следующими адресами:

40h - канал 0;
41h - канал 1;
42h - канал 2;
43h - управляющий регистр.
Приведем формат управляющего регистра:

7 6 5 4 3 2 1 0
T-T-T-T-T-T-T-¬
¦ ¦ ¦  ¦ ¦
LT+T+T+T+T+-+T+T-
LT- LT- L=T=- L= BCD: 0 - двоичный счет;
  ¦ ¦    ¦        1 - двоично-десятичный счет.
  ¦ ¦    ¦
  ¦ ¦    L===== M: 000 - режим 0;
  ¦ ¦                001 - режим 1;
  ¦ ¦                X10 - режим 2;
  ¦ ¦                X11 - режим 3;
  ¦ ¦                100 - режим 4;
  ¦ ¦                101 - режим 5.
  ¦ ¦
  ¦ L========== RW:  00 - код команды CLC (запомнить CE);
  ¦                    01 - чтение/запись старшего байта;
  ¦                    10 - чтение/запись младшего байта;
  ¦                    11 - чтение/запись младшего, затем
  ¦                      старшего байта.
  ¦             
L============== SC:  00 - канал 0;
01 -    канал 1;
10 -    канал 2;
11 -    код команды RBC (чтение состояния канала).



Поле BCD определяет формат константы, использующейся для счета - двоичный или двоично-десятичный. В двоично-десятичном режиме константа задается в диапазоне 1-9999.

Поле M определяет режимы работы микросхемы 8254:

0 - прерывание от таймера;
1 - программируемый ждущий мультивибратор;
2 - программируемый генератор импульсов;
3 - генератор меандра;
4 - программно-запускаемый одновибратор;
5 - аппаратно-запускаемый одновибратор.
Мы будем рассматривать только режим 3, так как именно он используется в каналах 0 и 2.

Поле RW определяет способ загрузки констант через однобайтовый порт. Если в этом поле задано значение 00, это управляющее слово будет использоваться для фиксации текущего содержимого регистров счетчика CE в буферном регистре OL с целью чтения программой. Это код команды CLC - фиксация регистров. Код канала, для которого будет выполняться фиксация, должен быть указан в поле SC. Поля M и BCD при этом не используются.

Поле SC определяет номер канала, для которого предназначено управляющее слово. Если в этом поле задано значение 11, будет выполняться чтение состояния канала.

Приведем формат команды RBC чтения слова состояния канала:

7 6 5 4 3 2 1 0
T-T-T-T-T-T-T-¬
¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
LT+T+T+T+T+T+T+T-
LT- ¦ ¦ ¦ ¦ ¦ L= равно 0.
  ¦  ¦ ¦ ¦ ¦ ¦
  ¦  ¦ ¦ ¦ ¦ L=== 1 - выбор канала 0.
  ¦  ¦ ¦ ¦ L===== 1 - выбор канала 1.
  ¦  ¦ ¦ L======= 1 - выбор канала 2.
  ¦  ¦ ¦
  ¦  ¦ L========= STAT: 0 - читать состояние каналов;
  ¦  ¦                  1 - не читать состояние каналов.
  ¦  ¦
  ¦  L=========== CNT:  0 - запомнить текущее содержимое CE;
  ¦                  1 - не запоминать содержимое CE.
  ¦
  L============== код команды RBC - 11.



С помощью этой команды вы можете выполнять операции чтения состояния каналов либо запоминание регистра счетчика CE каналов. Можно выполнять эти операции как для отдельных каналов, так и для всех каналов одновременно, если установить соответствующие биты (1, 2, 3) в 1.

Формат слова состояния канала напоминает формат регистра управляющего слова, за исключением двух старших разрядов 7 и 6:

7 6 5 4 3 2 1 0
T-T-T-T-T-T-T-¬
¦ ¦ ¦ ¦  ¦ ¦
LT+T+T+T+T+-+T+T-
¦ ¦ LT- L=T=- L= BCD: 0 - двоичный счет;
¦ ¦  ¦    ¦        1 - двоично-десятичный счет.
¦ ¦  ¦    ¦
¦ ¦  ¦    L===== M: 000 - режим 0;
¦ ¦  ¦                001 - режим 1;
¦ ¦  ¦                X10 - режим 2;
¦ ¦  ¦                X11 - режим 3;
¦ ¦  ¦                100 - режим 4;
¦ ¦  ¦                101 - режим 5.
¦ ¦  ¦
¦ ¦  L========== RW:  00 - код команды CLC (запомнить CE);
¦ ¦                01 - чтение/запись старшего байта;
¦ ¦                10 - чтение/запись младшего байта;
¦ ¦                11 - чтение/запись младшего, затем
¦ ¦                        старшего байта.
¦ ¦         
¦ L============= FN:  флаг перезагрузки констант;
L=============== OUT: состояние выхода OUT.




Разряд FN используется, в основном, в режимах 1 и 5 для определения, произошла ли загрузка константы из регистра CR в регистр счетчика CE.

Разряд OUT позволяет определить состояние выходной линии канала OUT в момент выполнения команды RBC.

Для программирования канала таймера необходимо выполнить следующую последовательность действий:

вывести в порт управляющего регистра с адресом 43h управляющее слово;
требуемое значение счетчика посылается в порт канала (адреса 40h...42h), причем вначале выводится младший, а затем старший байты значения счетчика.
Сразу после этого канал таймера начнет выполнять требуемую функцию.

Для чтения текущего содержимого счетчика CE необходимо выполнить следующее:

вывести в порт управляющего регистра код команды CLC (команда запоминания содержимого регистра CE);
вывести в порт управляющего регистра код команды запроса на чтение/запись в регистры канала (поле RW должно содержать 11);
двумя последовательными командами ввода из порта нужного канала ввести младший и старший байты текущего сосотояния счетчика CE.
Для чего вам может понадобиться перепрограммирование каналов таймера?

Если вам надо повысить точность измерения времени, выполняемого с помощью канала 0 таймера, вы можете увеличить частоту генерируемых этим каналом импульсов (стандартно 18,2 Гц). По окончании измерений режим работы канала необходимо восстановить для правильного функционирования системы.


Попробую поковыряться.

ЗЫ Попойму у Vesper'a так же опечатка в примере програмы:
Цитата
time:=meml[0:6C];
proces;
time:=meml[0:6C]-time;
if time<0 then time:=time+00B0;
writeln(time/18.2:6:2);
Shub
Мои попытки ускорить таймер потерпели фиаско.
Port[$43]:=6; - здесь по-мойму ошибка, не шестерку туда нужно отправлять, а вот что?
У кого какие мысли?
Altair
Вот, что у Питера Нортона написано:
Цитата
Одна из  скрытых возможностей IBM/PC - это наличие программируемого таймера. Он не измеряет никаких промежутков времени, а только подсчитывает импульсы основного тактового генератора системы. Для этого таймера задается число, называемое коэффициентом деления (или просто делителем), и он подсчитывает число импульсов тактового генератора и сравнивает это число с делителем. Когда эти числа сравняются, таймер выдает сигнал и снова начинает подсчет с нуля.

Системный тактовый генератор работает на частоте 1,19МГц.  Так что если таймер запрограммирован на
 10000 импульсов, он будет выдавать сигналы примерно 100 раз в секунду. Можно добиться любой частоты импульсов на выходе таймера, подбирая соответствующий делите
ь. После загрузки делителя схемы динамика запускаются для работы под управлением таймера, после чего выходные сигналы таймера будут управлять частотой формируемого звука, а
компьютер может выполнять любую другую работу.

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

Листинг 3.2. может использоваться в качестве примера формирования звуков без использования таймера. Программа на ассемблере, представленная листингом 11.1, показывает, как запустить звучание динамика с помощью таймера.

Приложение 11.1. Текст программы генерации звука с использованием таймера (Ассемблер).

      a440seg segment 'code'
            assume cs:a440seg
      a440  proc far
            mov    al,0b6h
            out    67,al
            mov    ax,2711
            out    66,al
            mov    al,ah
            out    66,al
            in  al,97
            or  al,03
            out    97,al
            int    20h
      a440  endp
      a440seg  ends
            end



Т.е. программировать таймер все-таки можно.
p.s. я тоже думаю, что ошиька в
Port[$43]:=6;
Может перепробовать все по очереди?
Shub
Е-е-е, получилось. Значиться так:

43h - управляющий регистр:

7 6 5 4 3 2 1 0
T-T-T-T-T-T-T-
¦ ¦ ¦ ¦ ¦
LT+T+T+T+T+-+T+T-
LT- LT- L=T=- L= BCD: 0 - двоичный счет;
¦ ¦ ¦ 1 - двоично-десятичный счет.
¦ ¦ ¦
¦ ¦ L===== M: 000 - режим 0;
¦ ¦ 001 - режим 1;
¦ ¦ X10 - режим 2;
¦ ¦ X11 - режим 3;
¦ ¦ 100 - режим 4;
¦ ¦ 101 - режим 5.
¦ ¦
¦ L========== RW: 00 - код команды CLC (запомнить CE);
¦ 01 - чтение/запись старшего байта;
¦ 10 - чтение/запись младшего байта;
¦ 11 - чтение/запись младшего, затем
¦ старшего байта.
¦
L============== SC: 00 - канал 0;
01 - канал 1;
10 - канал 2;
11 - код команды RBC (чтение состояния канала).

Следовательно, вводим такую последовательность – 00100110 или в десятичном 38, с права на лево: первый ноль – двоичный счет, 011 - генератор меандра, 10 - чтение/запись младшего байта, ну и последние 00 – потому что канал 0 используется в системных часах времени суток.

В паскалевском виде это выглядит так:
Port [$43]:=38;

И потом вводим максимальное значение для младшего байта 256:
Port [$40]:=256;

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

Менее шустный таймер у меня пока не получается :p2: – машина виснет. Но ковырять надо RW.
Shub
Сорри, в моем примере надо вводить не 256, а 1, то есть так:
Port [$43]:=38;
Port [$40]:=1;
Altair
Shub, высший класс!
А выше скорость не выйдет, port[$40]:=X, это значит поставить таймер БЕЗ замедления, т.е. если поставить port[$40]:=Max;, точно не знаю чему максимум равен, то время остановиться!
Guest
Цитата
Shub, высший класс!
А выше скорость не выйдет, port[$40]:=X, это значит поставить таймер БЕЗ замедления,


Спасибо smile.gif, я в курсе, что скорость выше не будет. Я имел в виду другое: в примере я записываю значение в младший байт, а значит максимальное значение это 256, то есть для того, что бы хотя бы восстановить значение таймера мне надо записать 655536, то есть задействовать старший байт – а это не выходит, висним. То, есть не удается замедлить таймер менее чем на 4,68 Гц (256).

Цитата
т.е. если поставить port[$40]:=Max;, точно не знаю чему максимум равен, то время остановиться!


Чавой-то я сомневаюсь <_<
Altair
нашел....
Пишем модуль для работы с системным таймером.

Цитата
Пишем модуль для работы с системным таймером

Часто при программировании в некоторых местах программы необходимо замерять время исполнения кода, в других просто останавливать выполнение не некоторое время. Например, если писать игру, необходимо создавать код, который бы ограничивал скорость игры. Конечно, если игра очень тяжелая, то некоторое время она может существовать без такого ограничителя. Но со временем вычислительная мощь компьютеров растет (к сожалению не сама по себе) и в игры без ограничителя скорости играть становится невозможно. Или вы решили написать бенчмарк для процессора. Тут уже нужны очень точные средства для замера времени исполнения кода. Таких примеров можно привести уйму. Проще сказать, что в любой более-менее серьезной программе измерение времени просто необходимо. К сожалению штатные средства в Паскале ограничиваются только процедурой Delay что описана в модуле CRT. Но она очень сильно зависит от производительности системы. Конечно, можно использовать процедуру GetTime, но она довольно громоздка. А стандартных процедур по замеру времени выполнения кода вообще нет.

Ну и не надо! Мы ведь не чайники? Конечно, не чайники! Сами напишем. При написании программ последовательный код стараются объединить в циклы. Код, повторяющийся в программе выносят в отдельные процедуры и функции. А код, который явно будет использоваться не в одной программе, выносят в модули. Мы так и сделаем. Давайте создадим в Паскале файл TIMER.PAS и начнем. Как известно название модуля и файла должны совпадать, поэтому пишем:

Unit Timer;


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


interface
procedure Start (var T:longint);
procedure Stop (var T:longint);
procedure Pause (T:longint; Show:boolean);


Итак, мы объявили три процедуры. Процедуры Start и Stop будут служить для измерения времени выполнения кода, а Pause станет заменой Delay. Переменная T - будит служить для передачи данных о времени. Show - для разрешения или запрещения вывода времени на экран. Далее следует исполнительная часть. Она служит для объявления локальных констант, переменных и типов. В данном модуле они нам не нужны:

Implementation


Далее следует самое интересное. Вы еще не задумывались каким же способом мы будем производить замер времени? А почему бы не использовать аппаратный таймер? Тем более это очень просто:

SystemTimer:longint absolute $0040:$006C;


Вот и все! Нет, модуль не весь, но мы имеем полный доступ к аппаратному таймеру, расположенному по физическому адресу $0040:$006C. Значение двойного слова по этому адресу увеличивается на единицу 18.2 раза в секунду и не зависит от производительности системы. Нам осталось только написать примитивные процедуры для оперирования с таймером:

procedure Start (var T:longint);
begin
T:=SystemTimer;
end;
procedure Stop (var T:longint);
begin
T:=SystemTimer-T;
end;

procedure Pause (T:longint; Show:boolean);
var Xn,Xt:longint;
begin
Xt:=0;
Xn:=SystemTimer;
While ((Xt-Xn)/18.2)*1000 < T do
begin
Xt:=SystemTimer;
If Show then
writeln((xt-xn)/18.2:6:4)
end;
end;


Ну, и долгожданный


end.


Все, компилируем. Хочется сразу проверить работу, не так ли?

Program TimerPrimer;
uses timer;
Var i : integer;
a :Real;
Time : LongInt;
begin
Randomize;
Start(Time);
For i:=1 to 30000 do
a:=Sin(sqrt(i))*Cos(sqrt(Random(10000)));
Stop(Time);
Writeln('Время выполнения: ',Time/18.2:6:4);
Readln;
Pause(10000, True);
end.



Данная программа демонстрирует возможности модуля Timer. В начале она исполняет цикл от 1 до 30000 в котором высчитывает значение а. Время выполнения этого цыкла и замеряют наши процедуры Start и Stop. После чего, дождавшись нажатия на Enter делаем паузу на 10.000 секунд с разрешаем процедуре Pause осуществлять вывод на экран.

Теперь вы сможете использовать точный таймер в своих программах. А почему же я не воспользовался процедурой GetTime? Только из-за ее громоздкости? Конечно нет. Посмотрите на код. Что мы собственно использовали? Только прямой доступ к физическому адресу аппаратного таймера. Так кто мешает использовать его в других языках программирования? Вот тут то и оно.
xds
f = 1193180 Гц / c

где с = коэффициент деления (число 0..65535).

Отсюда

f[max] = 1193180 Гц / 1 = 1193180 Гц (T[min] ~= 838 нс);
f[min] = 1193180 Гц / 0 (?) = 1193180 Гц / 65536 ~= 18.2 Гц (T[max] ~= 54,9 мс).

Почему 65536 = 0? Логика работы таймера заключется в уменьшении на 1 по каждому тактовому импульсу значения во внутреннем 16-разрядном регистре, предварительно инициализированном коэффициентом деления. При переполнении, микросхема таймера выполняет некоторые манипуляции - в зависимости от режима, на который она запрограммирована. Т. о. максимальный интервал между последовательными переполнениями счетчика достижим при коэффициенте деления 0. Исходя из того, что частота тактирования таймера ~1193180 Гц, получаем указанные выше формулы и диапазоны частоты и периода.

Напрямую таймер в среде Windows не программируется, поскольку ОС монопольно использует его для переключения нитей. Тем не менее, Windows эмулирует в DOS-приложениях таймер и IRQ0 (правда, для некоторого "разумного" диапазона частот).
xds
Пример программирования таймера (используется для получения частоты кадров 72 FPS).
Aelita
А как переделать Вашу программу, если нужно еще в 4 раза быстрее (т.е. в 16 раз быстрее стандартных 18 Гц)?

И не повлияют ли дополнительные прерывания на системное время?

Извините за вопрос, системные прерывания и ассемблер для меня сложноваты. unsure.gif
Lapp
Цитата(Aelita @ 14.11.2010 5:13) *
А как переделать Вашу программу, если нужно еще в 4 раза быстрее (т.е. в 16 раз быстрее стандартных 18 Гц)?

И не повлияют ли дополнительные прерывания на системное время?

Извините за вопрос, системные прерывания и ассемблер для меня сложноваты. unsure.gif

Aelita, скажи, пожалуйста, под какой OS ты работаешь? Насколько я понимаю, все, что написано выше - написано для давно не используемой системы MS DOS. Под Windows (я имею в виду версии старше W95) это работать не будет. Нужны совсем другие методы. И хотя никакого влияния на системное время можно не опасаться, ничего хорошего тоже можно не ожидать. Если, конечно, ты не работаешь на старой системе (что я КРАЙНЕ не рекомендовал бы, поскольку ты рискуешь похоронить все преимущества новых процессоров (в основном - многоядерность).

Короче, я рекомендую тебе вместо реанимирования давно умерших тем начать НОВУЮ ТЕМУ в разделе 32-битные компиляторы. И в ней дать все детали твоего софта.
Aelita
Я работаю, как ни странно, под DOS32 (Free Pascal).
Это связано с тем, что программы были написаны мною для научных целей довольно давно, а переделывать их под Дельфи влом. Кроме того, Винда жрет немало памяти, что для моих программ критично (2 гига нужны полностью), да и с устойчивостью под Виндой иногда возникают проблемы.

Что касается многоядерности, я просто не знаю, умеет ли ее реально использовать Free Pascal.
Плюс нет уверенности, что при компиляции программ в многоядерном режиме они будут работать устойчиво.

В общем, DOS (в расширении 32 bit) IMHO еще рано хоронить.

Когда появится время. подумаю о переделке программ под многоядерность и 64 бита.
Но даже в этом случае я, если бы была такая возможность, предпочла бы работать с командной строкой
(точнее, с собственной минимальной графической оболочкой) без тормозов, ненужных потерь ресурсов и сбоев. unsure.gif
Lapp
Цитата(Aelita @ 15.11.2010 7:01) *
Я работаю, как ни странно, под DOS32 (Free Pascal).
Это связано с тем, что программы были написаны мною для научных целей довольно давно, а переделывать их под Дельфи влом. Кроме того, Винда жрет немало памяти, что для моих программ критично (2 гига нужны полностью), да и с устойчивостью под Виндой иногда возникают проблемы.

Что касается многоядерности, я просто не знаю, умеет ли ее реально использовать Free Pascal.
Плюс нет уверенности, что при компиляции программ в многоядерном режиме они будут работать устойчиво.

В общем, DOS (в расширении 32 bit) IMHO еще рано хоронить.

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

Это все хорошо. Никто не заставляет тебя переходить на Windows, или еще что-то в этом роде. Тебе совершенно не нужно доказывать преимущества системы или обосновывать свой выбор (кстати, FPC самым замечательным образом работатет с многоядерностью; только нужен не режим, а специально организованная архитектура твоей программы). Я просто просил тебя уточнить условия задачи. В любом случае, если речь касается FPC, я тебе все же еще раз настоятельно рекомендую создать новую тему в разделе "32-битные компиляторы". А в разделе Теория твоей сугубо практической задаче делать совершенно нечего. И, ко всему - не надо поднимать старые чужие темы (читай Правила). Если находишь, что какая-то тема может быть полезна в обсуждении твоей проблемы - отлично, дай на нее ссылку!

Желаю успеха, и - до встречи в новой теме!
Интересно, однако, бывали ли тут вообще вопросы про про FPC под DOS GO32 (я правильно понимаю, что речь идет о нем?)
TarasBer
Что же это за код такой, что его работа так сильно зависит от системы?

> Кроме того, Винда жрет немало памяти

200 мегабайт она жрёт. Остаётся ещё 1800 мегабайт. Что это за код, котоорому 1800 мало, а 2000 в самый раз?
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.