Как я Java учил, а потом 1С-у удивлялся

23.09.22

База данных - HighLoad оптимизация

Сравнение производительности 1С с Java.

Скачать файл

ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.

Наименование Бесплатно
Тестовая конфигурация
.cf 78,39Kb
11
11 Скачать бесплатно
Тестовые данные
.zip 35,78Mb
11
11 Скачать бесплатно

День добрый.

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

Но это лирика. Делая задачи, внезапно заметил, что выполняются они как - то слишком уж шустро. Сделал пример, запустил его в Java, а потом в 1С и крайне озадачился, сначало не понял, а потом как понял.

Итак, возьмем простой пример. Пусть у нас есть 12 текстовых файлов с данными о оборотах по магазинам за год.

 

 

На самом деле нам вообще пофиг, что там за данные, просто их должно быть много. У нас их будет 3 360 000 записей, раскиданных по 12 файлам. Надо вывести данные о оборотах по месяцам по одному из магазинов. Ок. Что мы делаем в 1С:

  1. Находим все файлы *.txt в каталоге, записываем их в массив
  2. Замеряем время начала в миллисекундах
  3. Создаем ТаблицуЗначений
  4. Проходим по каждому файлу, по каждой строке через ЧтениеТекста
  5. Каждую прочитанную строку разбиваем на массив по разделителю.
  6. Вставляем в таблицу значений, преобразуя строки к числам, строки к датам
  7. Замеряем время окончания и выводим его
  8. Получаем время чтения данных =26 506 мсек. (26 секунд, с отключенным отладчиком).

Вот этот код:

	МассивФайлов=НайтиФайлы("C:\Java_Projects\RawData","*.txt");
	ВремяНачала=ТекущаяУниверсальнаяДатаВМиллисекундах();
	ТаблицаДанных=Новый ТаблицаЗначений;
	ТаблицаДанных.Колонки.Добавить("ИмяМагазина");
	ТаблицаДанных.Колонки.Добавить("Дебит");
	ТаблицаДанных.Колонки.Добавить("Кредит");
	ТаблицаДанных.Колонки.Добавить("ДатаОперации");
	ТаблицаДанных.Колонки.Добавить("МесяцОперации");
	Для Каждого Файл Из МассивФайлов Цикл
		Чтение=Новый ЧтениеТекста(Файл.ПолноеИмя,КодировкаТекста.UTF8);
		ЭтоПерваяСтрока=Истина;
		Пока Истина Цикл
			ТекущиеДанныеТекстом=Чтение.ПрочитатьСтроку();
			Если ТекущиеДанныеТекстом=Неопределено Тогда
				Прервать;
			КонецЕсли;
			Если ЭтоПерваяСтрока=Истина Тогда
				ЭтоПерваяСтрока=Ложь;
				Продолжить;
			КонецЕсли;
			ТекущиеДанныеМассивом=СтрРазделить(ТекущиеДанныеТекстом,";");
			НоваяСтрока=ТаблицаДанных.Добавить();
			НоваяСтрока.ИмяМагазина=ТекущиеДанныеМассивом[0];
			НоваяСтрока.Дебит=Число(СтрЗаменить(ТекущиеДанныеМассивом[1],".",","));
			НоваяСтрока.Кредит=Число(СтрЗаменить(ТекущиеДанныеМассивом[2],".",","));
			ДатаСтрокой=ТекущиеДанныеМассивом[3];
			ДатаСтрокой=СтрЗаменить(ДатаСтрокой,"/",".");
			ДатаСтрокой=ДатаСтрокой+" 00:00:00";
			НоваяСтрока.ДатаОперации=Дата(ДатаСтрокой);
			НоваяСтрока.МесяцОперации=НачалоМесяца(НоваяСтрока.ДатаОперации);
		КонецЦикла;
	КонецЦикла;
	ВремяОкончания=ТекущаяУниверсальнаяДатаВМиллисекундах();
	Сообщить(СтрШаблон("Время чтения данных %1 мсек.",Строка(ВремяОкончания-ВремяНачала)));
	

Да, можно и схитрить, считать весь файл в строку, разбить строку в массив, НО.

Абсолютно тот же алгоритм мы сделаем в Java и получим 4973 мсек (5 секунд) вот так:

static void LoadFromFiles(URI resPath) {
        File dir = new File(resPath);
        FilenameFilter filter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                Boolean result = false;
                if (name.toUpperCase().endsWith(".TXT")) {
                    result = true;
                }
                return result;
            }
        };

        File[] txtFiles = dir.listFiles(filter);
        for (File txtFile : txtFiles) {
            String currentFileName = txtFile.getAbsolutePath();
            try {
                FileReader reader = new FileReader(currentFileName);
                BufferedReader bufReader = new BufferedReader(reader);
                Boolean firstLine = true;
                while (bufReader.ready()) {
                    String currentLine = bufReader.readLine();
                    if (firstLine) {
                        firstLine = false;
                        continue;
                    }
                    String[] arrayData = currentLine.split(";");
                    TrowData rowData = new TrowData();
                    rowData.name = arrayData[0];
                    rowData.debet = Double.parseDouble(arrayData[1]);
                    rowData.credit = Double.parseDouble(arrayData[2]);
                    SimpleDateFormat dateFormater = new SimpleDateFormat("dd/M/yyyy");
                    rowData.operationDate = dateFormater.parse(arrayData[3]);
                    Calendar calendar = new GregorianCalendar();
                    calendar.setTime(rowData.operationDate);
                    calendar.set(Calendar.DAY_OF_MONTH, 1);
                    rowData.operationMounth = calendar.getTime();
                    dataTable.add(rowData);
                }
            } catch (IOException fileExсpt) {
                System.out.println(String.format("Не удалось прочитать файл %s по причине %s", currentFileName, fileExсpt.getMessage()));
                return;
            } catch (ParseException dateExсpt) {
                System.out.println(String.format("Не удалось преобразовать данные о дате операции в формат даты по причине %s", dateExсpt.getMessage()));
                return;
            }
        }
    }

Ладно. Ладно, интерпретируемый код, виртуальная машина, такие дела.

Но пойдем дальше по задаче  - найти и вывести обороты по одному из магазинов по месяцам, например по Пятерочке. В 1С мы это сделаем так:

	ВремяНачала=ТекущаяУниверсальнаяДатаВМиллисекундах();
	СтруктураПоиска=Новый Структура;
	СтруктураПоиска.Вставить("ИмяМагазина","pyterochka");
	ТаблицаДанных.Индексы.Добавить("ИмяМагазина");
	МассивСтрок=ТаблицаДанных.НайтиСтроки(СтруктураПоиска);
	ФильтрованнаяТаблицаДанных=ТаблицаДанных.Скопировать(МассивСтрок);
	ФильтрованнаяТаблицаДанных.Свернуть("МесяцОперации","Дебит,Кредит");
	ВремяОкончания=ТекущаяУниверсальнаяДатаВМиллисекундах();
	Сообщить(СтрШаблон("Время обработки данных %1 мсек.",Строка(ВремяОкончания-ВремяНачала)));
	Сообщить(СтрШаблон("Обработано %1 записей.",Строка(ТаблицаДанных.Количество())));

Максимальное использование встроенных методов, ведь так? Ну, сделайте это быстрее. И мы получим время обработки данных 6 547 мсек.

Сделаем это на Java:

static void getTotalsPerMonthByShop(String shopName) {
        Map<Date, Double> tmpResult = dataTable.stream().
                filter(rowData -> rowData.name.equals(shopName)).
                sorted(Comparator.comparing(rowData -> rowData.operationMounth)).
                collect(Collectors.groupingBy(rowData -> rowData.operationMounth, Collectors.summingDouble(rowData -> rowData.debet - rowData.credit)));

        LinkedHashMap<Date, Double> sortedResult=tmpResult.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).
                collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                (e1, e2) -> e1, LinkedHashMap::new));

        System.out.println(String.format("Отчет о итоговой прибыли за каждый месяц по магазину %s",shopName));
        Calendar calendar=new GregorianCalendar();
        for (Map.Entry<Date, Double> rowData : sortedResult.entrySet()) {
            calendar.setTime((Date)rowData.getKey());
            String userFriendlyDate=String.format("%02d", calendar.get(Calendar.MONTH)+1)+"."+Integer.toString(calendar.get(Calendar.YEAR));
            DecimalFormat dF = new DecimalFormat( "#.00" );
            String userFriendlyValue=dF.format(rowData.getValue());
            System.out.println(userFriendlyDate + " : " + userFriendlyValue);
        }
    }

И получим время обработки данных 147 мсек.

Прошу, ткните меня туда, где я не прав.

 

Код Java доступен тут

https://github.com/Hadgehogs/FS3/blob/main/src/Lesson5/Expert/TfinReport.java

Тестовые данные и тестовая конфа будут прикреплены к публикации.

Версия 1С 8.3.21.1508, файловая, однако я пробовал и на 8.3.17 и на 8.3.20.

Компьютер I5-12600k, 64 ОЗУ 2300, SSD 980 Samsung, план питания - высокий, антивирус отключен, Windows 10.

Тестовые файлы следует извлечь в папку C:\Java_Projects\RawData, ну, либо поменять пути в примерах.

 

upd.

Убрал ненужное индексирование таблицы значений, время сократилось до 1616 мсек, но все равно разница с Java составляет 10 раз. Обновил cf-ник в примере

Быстродействие сравнение с Java производительность.

См. также

HighLoad оптимизация Программист Платформа 1С v8.3 Бесплатно (free)

Метод очень медленно работает, когда параметр приемник содержит намного меньше свойств, чем источник.

06.06.2024    9259    Evg-Lylyk    61    

44

HighLoad оптимизация Программист Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Анализ простого плана запроса. Оптимизация нагрузки на ЦП сервера СУБД используя типовые индексы.

13.03.2024    5097    spyke    28    

49

HighLoad оптимизация Программист Платформа 1С v8.3 Бесплатно (free)

Оказывается, в типовых конфигурациях 1С есть, что улучшить!

13.03.2024    7573    vasilev2015    20    

42

HighLoad оптимизация Инструменты администратора БД Системный администратор Программист Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Обработка для простого и удобного анализа настроек, нагрузки и проблем с SQL сервером с упором на использование оного для 1С. Анализ текущих запросов на sql, ожиданий, конвертация запроса в 1С и рекомендации, где может тормозить.

2 стартмани

15.02.2024    12419    241    ZAOSTG    80    

115

HighLoad оптимизация Системный администратор Программист Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Принимать, хранить и анализировать показания счетчиков (метрики) в базе 1С? Почему бы нет? Но это решение быстро привело к проблемам с производительностью при попытках построить какую-то более-менее сложную аналитику. Переход на PostgresSQL только временно решил проблему, т.к. количество записей уже исчислялось десятками миллионов и что-то сложное вычислить на таких объемах за разумное время становилось все сложнее. Кое-что уже практически невозможно. А что будет с производительностью через пару лет - представить страшно. Надо что-то предпринимать! В этой статье поделюсь своим первым опытом применения СУБД Clickhouse от Яндекс. Как работает, что может, как на нее планирую (если планирую) переходить, сравнение скорости работы, оценка производительности через пару лет, пример работы из 1С. Все это приправлено текстами запросов, кодом, алгоритмами выполненных действий и преподнесено вам для ознакомления в этой статье.

1 стартмани

24.01.2024    5669    glassman    18    

40

HighLoad оптимизация Программист Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Встал вопрос: как быстро удалить строки из ТЗ? Рассмотрел пять вариантов реализации этой задачи. Сравнил их друг с другом на разных объёмах данных с разным процентом удаляемых строк. Также сравнил с выгрузкой с отбором по структуре.

09.01.2024    14010    doom2good    49    

71
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. RustIG 1747 23.09.22 09:46 Сейчас в теме
мда.... коллега.....
1) такие задачи в 1с по другому решаются
2) "Код Java доступен тут" - написать код на яве самому без гитхаба сможете? отладить? сопровождать в последствии при изменениях? передать код другому специалисту? насколько быстро сможете все это выполнять: писать код, отлаживать, сопровождать изменения, передать под доработку другому?

тут вот МТС рассказывает что они используют в своих разработках
https://www.youtube.com/watch?v=S9EnpxCZtWU
Каждый продукт создается на своем языке/ках, у каждого продукта свое назначение и решаемые задачи

А вообще, молодца, что яву учите и тут выкладываете вопросы - очень познавательно
almierm; A1WEB; +2 Ответить
2. user1559729 23.09.22 09:54 Сейчас в теме
(0) Я не понял суть удивления... Вроде бы всё так же, как и было))).
Irwin; Jeka44; Patriot1S; +3 Ответить
13. Patriot1S 101 23.09.22 12:58 Сейчас в теме
(2) Так теперь автору стало понятно чем различается динамическая и статическая типизация. Да на порядок в скорости ;)
3. cdiamond 235 23.09.22 10:02 Сейчас в теме
Сделай точно такой же замер на одноядерной машине (подними виртуалку). Ни на что не намекаю, просто интересно
4. kembrik 10 23.09.22 10:05 Сейчас в теме
Коллега, вам в копилку использование произвольных отборов в ТЗ более гуманным способом, не благодарите

ТаблицаОстатков = МассивРезультатовОстатков[0].Выгрузить(); 
	
	Построитель = Новый ПостроительЗапроса;
	Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТаблицаОстатков);
	
	Если ПроверяемФормыОплаты Тогда
		Отбор = Построитель.Отбор;
		ОтборНаименование = Отбор.Добавить("ФормаОплаты");
		
		ОтборНаименование.ВидСравнения	= ВидСравнения.ВСписке;
		ОтборНаименование.Использование	= Истина;
		ОтборНаименование.Значение		= ФормыОплаты;   
	КонецЕсли;         
	
	Если ПроверяемСоглашения Тогда 
		Если ЗначениеЗаполнено(ТекСтруктураСписков.СписокСоглашенийКлиент) Тогда
			Отбор = Построитель.Отбор;
			ОтборНаименование = Отбор.Добавить("Соглашение");
			ОтборНаименование.ВидСравнения	= ВидСравнения.ВСписке;
			ОтборНаименование.Использование	= Истина;
			ОтборНаименование.Значение		= ТекСтруктураСписков.СписокСоглашенийКлиент;   
		КонецЕсли;  
		
		Если ЗначениеЗаполнено(ТекСтруктураСписков.СписокСоглашенийКлиентИсключить) Тогда
			Отбор = Построитель.Отбор;
			ОтборНаименование = Отбор.Добавить("Соглашение");
			ОтборНаименование.ВидСравнения	= ВидСравнения.НеВСписке;
			ОтборНаименование.Использование	= Истина;
			ОтборНаименование.Значение		= ТекСтруктураСписков.СписокСоглашенийКлиентИсключить;   
		КонецЕсли;  
	КонецЕсли;
	
	Построитель.Выполнить();
	ТаблицаЗначенийОстатков = 	Построитель.Результат.Выгрузить();
Показать


Текст в вашем случае лучше читать через ЧтениеДанных.ПрочитатьСтрокуАсинх
Потоки наше всё
6. dmpas 418 23.09.22 10:26 Сейчас в теме
(4) а время-то? время померяли :) дюже любопытно
9. kembrik 10 23.09.22 11:40 Сейчас в теме
(6) На эту пятницу прокрастинация уже подкралась, но на след. неделе потестю варианты, тоже стало интересно
5. RocKeR_13 1366 23.09.22 10:15 Сейчас в теме
Из разряда "Как я метал слушал, а потом попсе удивлялся"))
acvatoris; Irwin; Prometeus2011; gigapevt; triviumfan; +5 Ответить
7. dabu-dabu 307 23.09.22 10:32 Сейчас в теме
То, что 1С медленнее вообще не удивительно. Но есть плюсы, собственно выходящие из минусов, и ключевой в примере - слишком мало и слишком примитивные объекты для работы с данными. Но зато все просто и понятно для программиста.

Но и в вашем примере косяки:
1. Не типизированные колонки таблицы
2. Зачем делать индексирование для единичного отбора, в коде Java не увидел индексирования
3. Сравнение строк в 1С всегда было очень не быстрой функцией. Можно попробовать сделать мэпинг названий магазинов в числовой формат или в УИД
4. Неправильное применение метода Скопировать, надо: ТаблицаДанных.Скопировать(СтруктураПоиска)

Как видим, даже имея очень простые объекты и методы для работы с данными можно сделать "косячно".
Если 1С эти объекты и методы будет сильно развивать, то косяков будет кратно больше, с учетом квалификации программистов 1С и желаний Заказчиков на быстро-дешево.
Tavalik; Irwin; Yashazz; ixijixi; +4 Ответить
12. Hadgehogs 493 23.09.22 12:56 Сейчас в теме
(7)
1. Типизация ничего не дает.
2. Принято, тут это бессмысленно, все равно единичный поиск, построение индекса вызовет тот же проход по таблице, что и простой перебор + построение дерева.
3. Это проблемы 1С, на которые, возможно, стоит обратить внимание 1С.
4. Учту, но производительности это не добавит. Основная прибавка будет в пункте 2.
20. sereginseregin 22 25.09.22 09:55 Сейчас в теме
(12)
Коректнее искать ответ не в типизации, а в компиляции. Предположу, что расстановка указателей Java для
rowData.debet
осуществляется при компиляции. А в 1с для
НоваяСтрока.Дебит
в процессе выполнения кода. Отсюда и разница в производительности.
8. alex_bob 248 23.09.22 11:35 Сейчас в теме
В 1С используются собственные числовые типы, с гарантированной точностью ~ 24 значащие цифры. В java Вы используете Double, точность которого зависит от разрядности платформы. При арифметических расчетах хорошо теряются копейки.
10. kembrik 10 23.09.22 11:44 Сейчас в теме
Я смотрю тема многих настолько заинтересовала, что ошибку в написании Дебета не заметили)
11. a_a_burlakov 288 23.09.22 12:43 Сейчас в теме
Ну таких сравнений можно наклепать очень много. :)

Притом и в пользу 1С тоже. Например, самописный небольшой проект с БД, отчетностью, работой нескольких пользователей и подключением небольшого самописного мобильного приложения. Кто выиграет по бюджету и человекочасам: 1С или Java? Зато алгоритмическая эффективность всегда на стороне Java, тут бесспорно; правда бизнес это обычно не волнует.
afk; Tavalik; mevgenym; Irwin; Dimkasan; +5 Ответить
14. axus 35 23.09.22 14:33 Сейчас в теме
В 1С не используется JIT (насколько я знаю), как в Java или в том же C#
Поэтому сравнивать скорость выполнения тут просто некорректно.
15. SerVer1C 815 23.09.22 17:24 Сейчас в теме
Странное сравнение разных весовых категорий. В эске используется простая ужасно медленная стековая виртуальная машина. А вирт. машина Java имеет почти все плюшки современных технологий. Да и не нужна скорость в 1с. Она создавалась как прослойка для отображения данных из БД, т. е. это ORM система, а не полноценный ЯВУ.
Irwin; Prometeus2011; +2 1 Ответить
16. Hadgehogs 493 23.09.22 17:44 Сейчас в теме
(15) Ужасно медленную стековую машину научили быстро решать линейные уравнения. Что мешает сделать также с таблицами значений, к примеру?
22. SerVer1C 815 26.09.22 11:54 Сейчас в теме
(16) Насколько понимаю, РСЛУ работает по такому принципу: заполняется объект, а все расчеты уже идут под капотом (нативно рассчитывается алгоритмами, написанными на Си[++]). Это сильно отличается от того, если бы РСЛУ описали бы кодом 1С.
17. Danil.Potapov 517 23.09.22 23:06 Сейчас в теме
как вариант немного ускорить первую часть
1. убрать проверку ЭтоПерваяСтрока в цикле

Чтение = Новый ЧтениеТекста(Файл.ПолноеИмя, КодировкаТекста.UTF8);
ТекущиеДанныеТекстом = Чтение.ПрочитатьСтроку();
ТекущиеДанныеТекстом = Чтение.ПрочитатьСтроку();
Пока ТекущиеДанныеТекстом <> Неопределено Цикл

2. Пусть платформа сама преобразует строку в число
ТаблицаДанных.Колонки.Добавить("Дебит", Новый ОписаниеТипов("Число"));
ТаблицаДанных.Колонки.Добавить("Кредит", Новый ОписаниеТипов("Число"));
...
.Дебит = Число(СтрЗаменить(ТекущиеДанныеМассивом[1],".",","));
заменить на
.Дебит= ТекущиеДанныеМассивом[1];
.Кредит = ТекущиеДанныеМассивом[2];

3. немного ускорить преобразование из строки в дату
ДатаСтрокой = СтрРазделить(ТекущиеДанныеМассивом[3], "/");
НоваяСтрока.ДатаОперации = Дата(ДатаСтрокой[2], ДатаСтрокой[1], ДатаСтрокой[0]);
МихаилМ; tormozit; +2 Ответить
18. tormozit 7229 24.09.22 09:20 Сейчас в теме
Назначение простого типа колонке приводит к замедлению, т.к. дополнительное время уходит на заполнение пустыми значениями соответствующего типа каждой добавленной строки. А вот при сериализации назначение простых типов колонкам дает заметную экономию в размере получаемой строки.
В остальном полезные замечания.
Danil.Potapov; +1 Ответить
19. tormozit 7229 24.09.22 10:42 Сейчас в теме
Автор статьи небрежно упомянул "с отключенным отладчиком". А ведь "отключенный отладчик" и "запрет отладки" - две большие разницы.
Для легкого кода основное замедление добавляет именно разрешение отладки, а не подключеннный отладчик.
Т.к. на сервере включать/отключать разрешение отладки часто хлопотно. Я доработал тест для выполнения по умолчанию на толстом клиенте. Также наглядно показал как правильно запрещать в клиенте отладку.
В тесте 2 кнопки -
Эталон - запускает оригинальный код
Турбо - запускает оптимизированный код (в том числе позамечаниям (17))
Флажок "Быстрый код" - выполняет код в однострочном варианте и таким образом демонстрирует низкое влияние разрешения отладки на такой вариант кода.
Оптимизация вычисления итогов не выполнялась, т.к. она уже оптимальна =)

Результаты на наборе файлов (840000 строк) на клиенте
report_01_2012.txt
report_02_2012.txt
report_03_2012.txt

РазрешенаОтладка БыстрыйКод Эталон Турбо
Нет Нет 15,00 11,00
Нет Да 14,00 10,30
Да Нет 30,00 23,00
Да Да 14,50 10,70

Как видно из результатов моего теста, вариант "с отключенным отладчиком" может давать в 2 раза отличающиеся скорости.
Теперь хочется чтобы автор сравнил результаты с аналогичным прогоном в Java
Прикрепленные файлы:
ТестоваяОбработка.epf
afk; ubnkfl; almierm; sewell; triviumfan; JohnyDeath; ktb; +7 Ответить
21. tormozit 7229 25.09.22 23:13 Сейчас в теме
(19) Под это дело даже написал статью про однострочный код https://infostart.ru/public/1732527/
23. Ndochp 103 27.09.22 10:36 Сейчас в теме
Ключевая ошибка тут в " У нас их будет 3 360 000 записей, раскиданных по 12 файлам. Надо вывести данные о оборотах по месяцам по одному из магазинов. Ок. Что мы делаем в 1С"

Нет, в 1С мы делаем не это. Мы грузим эти данные в оборотный регистр и после этого формируем отчет по оборотам столько раз и с теми отборами что захотим. И вот уже скорость работы отчета можно сравнивать со скоростью явы. А то пока получается "я написал файловую БД на бейсике, а она тормозит"
FreeArcher; afk; olexi2012; yaguarrr; almierm; Cерый; ovasiliev; sewell; +8 Ответить
Оставьте свое сообщение