И еще, тезисно:
- Файлов к скачиванию здесь пока не будет. Все еще весьма сыро. Основной целью публикации является дискуссия.
- Тесты проводились для win32::v8.3.4.365 - v 8.3.6.2237. Для win64, lin32, lin64 и для v8.2.19.130 только оценена возможность – работать будет при минимальных изменениях.
- В процессе экспериментов ни один проприетарный байт не пострадал. Т.е. оригинальные бинарники 1С я не патчу ни на диске, ни в памяти.
- Все исключительно в познавательных целях и с целью модификации программы для улучшения ее характеристик в соответствии с ее предназначением. Все имена вымышлены и совпадения случайны.
- 1С начал изучать вплотную недавно, много еще не знаю. До этого VB++C/C++. Целью работ был перенос части возможностей VB в 1С.
- Также заранее прошу прощения за стиль изложения – эпистолярный жанр не мой конек.
Итак...
Решаем простейшую задачу: Есть ТаблицаЗначений и в ней колонки [количество] и [цена]. Нужно вычислить СУММА([количество] * [цена]).
Здесь и далее время выполнения алгоритма засекается через ::GetTickCount(), т.е. является относительной величиной.
Код 1С:
Функция СчитатьТаблицу(Котейнер)
перем элем;
перем итого;
итого = 0;
для каждого элем из Котейнер цикл
итого = итого + элем[1] * элем[2];
КонецЦикла;
возврат итого;
КонецФункции
Код Си (иллюстративно):
...
cRows = pITable->getRowCount();
for (iRow = 0; iRow < cRows; iRow++)
{
if (pITable->getValueAt(iRow, 1).getIValue()->getNumeric(numQty) &&
pITable->getValueAt(iRow, 2).getIValue()->getNumeric(numPri))
{
numTot += numQty * numPri;
}
}
pOutRetValValue->assign(numTot);
...
Результаты:
*----------- сумма произведений по ТаблицеЗначений ---------------
* строк = 100 000
* посчитано СУММА([кол]*[цена]) в Си: результат = 333 328 333 350 000, время = 94
* посчитано СУММА([кол]*[цена]) в 1С: результат = 333 328 333 350 000, время = 547
* ИТОГ: подсчет в Си быстрее чем в 1С в 5,8 раз.
*----------- ------------------------------------- ---------------
Обратите внимание, что я работаю методами платформы с типами данных платформы и разница не на ...%, а в ...раз.
Сортируем..
Для опытов возьмем простейший алгоритм сортировки вставками. Исходные данные – массив случайных чисел. Простейшие алгоритмы будут безусловно сортировать по возрастанию. Также сделаем функции, принимающие предикат.
1С:
Процедура СортироватьМассивВставкамиВозр(М)
перем колво;
перем к;
перем к_этот;
перем к_пред;
перем елмЭтот;
перем елмПред;
колво = М.Количество();
для к = 1 по колво - 1 цикл
к_этот = к;
пока к_этот >0цикл
к_пред = к_этот -1;
елмЭтот = М[к_этот];
елмПред = М[к_пред];
если елмЭтот < елмПред тогда
М[к_этот]= елмПред;
М[к_пред]= елмЭтот;
иначе
Прервать;
КонецЕсли;
к_этот = к_пред;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Си:
...
for(int i = 1; i < iSize; i++){
for(int j = i; j > 0; j--){
mValThis = pParaArrIArr->getAt(j);
mValPrev = pParaArrIArr->getAt(j - 1);
if( mValThis < mValPrev){
pParaArrIArr->setAt(j - 1, mValThis);
pParaArrIArr->setAt(j, mValPrev);
}
else{
break;
}
}
}
...
Для Си, конечно, несколько многословно, но я намеренно показываю, что код переносится чуть-ли не один в один.
С++:
...
v8com_array_iterator _First(pParaArrIArr, 0);
v8com_array_iterator _Last(pParaArrIArr, pParaArrIArr->size());
std::sort(_First, _Last);
...
1С с предикатом:
процедура СортироватьМассивВставкамиПред(М, Предикат)
перем колво;
перем к;
перем к_этот;
перем к_пред;
перем елмЭтот;
перем елмПред;
перем елмЭтотМеньшеПред;
колво = М.Количество();
для к =1по колво -1цикл
к_этот = к;
пока к_этот >0цикл
к_пред = к_этот -1;
елмЭтот = М[к_этот];
елмПред = М[к_пред];
выполнить("елмЭтотМеньшеПред = " + Предикат + "(елмЭтот, елмПред)");
если елмЭтотМеньшеПредтогда
М[к_этот]= елмПред;
М[к_пред]= елмЭтот;
иначе
Прервать;
КонецЕсли;
к_этот = к_пред;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
В параметр предикат передаем "МодТест.СравнитьЗначенияНаМеньше"
Функция СравнитьЗначенияНаМеньше(знач знач1, знач знач2) экспорт
возврат ?(знач1 < знач2, истина, ложь);
КонецФункции
Для компоненты с предикатом посложнее... Делаем объект «Делегат»:
перем<делегат>;
<делегат> = <компонент>.СоздатьДелегат(<объект>[.ЭтотОбъект],“<имя-метода>”);
<делегат>.Вызвать(<список-параметров-метода>);
Т.е. создаем в компоненте объект инициализируемый ссылкой на объект (модуль) платформы и строкой с указанием, какой из методов (функций или процедур) вызвать с переданными при вызове параметрами. У этого объекта-делегата единственное назначение – передать параметры и управление в указанный метод объекта платформы (и вернуть результат, если это функция). Платформа не поддерживает конструкции вида <имя-переменной>(...), поэтому, после некоторых раздумий, я решил не фиксировать какой-либо определенный синтаксис. Делегат можно .Вызвать(...), .Выполнить(...), .Обработать(...), хоть .СделайМнеОоЛаЛа(...), в зависимости от семантики.
Процедура ТестДелегата()
сообщить("*---------- ТестДелегата -----------");
хДел = СПП.СоздатьДелегат(МодТест,"ТестДелегата_Функция");
хРез = хДел.Вызвать(202);
сообщить("* Делегат вызван, результат: " + хРез);
сообщить("*---------- ------------ -----------");
КонецПроцедуры
Функция ТестДелегата_Функция(пара)экспорт
сообщить("* вызов делегата, аргумент = "+ пара);
возврат пара +1;
КонецФункции
*---------- ТестДелегата -----------
* вызов делегата, аргумент = 202
* Делегат вызван, результат: 203
*---------- ------------ -----------
С учетом этого код сортировки плюсами c предикатом выглядит как то так:
...
v8com_array_iterator _First(pParaArrIArr, 0);
v8com_array_iterator _Last(pParaArrIArr, pParaArrIArr->size());
v8com_compare_callback _Comp(pParaIDelegat);
std::sort(_First, _Last, _Comp);
...
Результаты:
*-------------------- сортировка массива ---------------------
*Количество элементов массива: 1 500
*{802, 974, 846, 95, 355, 377, 933, 222, 968, 64, 198, 395, 966, ..., 136, 611, 161}
* сортировка 1С вставками, возр. == 3 078
* сортировка Си вставками, возр. == 329
* сортировка С++ вставками, возр. == 235
* сортировка С++ std::sort, возр. == 17
* С++ std::sort с предикатом, возр. == 94
* Си вставками с предикатом, возр. == 2 064
* 1С вставками с предикатом: == 56 969
*
*Проверка массививов: ОК
*{1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, ..., 992, 993, 994, 996, 996, 997, 997, 999}
*
* С вставками быстрее, чем 1С вставками в 9,4 раз
* С++ вставками быстрее, чем 1С вставками в 13,1 раз
* С вставками быстрее, чем С++ вставками в 0,7 раз
* С++ сорт быстрее, чем 1С вставками в 181,1 раз
* С++ сорт быстрее, чем С вставками в 19,4 раз
* С++ сорт быстрее, чем С++ вставками в 13,8 раз
* С++ сорт быстрее, чем С++ сорт с предикатом в 5,5 раз
* С++ сорт с предикатом быстрее, чем 1С сорт вставками в 32,7 раз
* С сорт вставками с предикатом быстрее, чем 1С сорт вставками в 1,5 раз
* 1С вставками с предикатом медленнее, чем 1С сорт вставками в 18,5 раз
* 1С вставками с предикатом медленнее, чем Си вставками с предикатом в 27,6 раз
* 1С вставками с предикатом медленнее, чем С++ сорт с предикатом в 606,1 раз
*-------------------- ------------------ ---------------------
Замечу, что количество элементов массива подобрано для получения некоторых характерных значений. Если их порядка 300, то с std::sort сравнить не получается – там просто 0. Если порядка 5000, то ждать пока 1С отсортирует с предикатом приходится неприемлемо долго. Графики в зависимости от количества элементов массива, конечно, были бы любопытны. Также интересно поведение std::_Insertion_sort(), влияние InterlockExhangeXXX, сравнение с контейнером над тривиальными типами данных, сравнение разных компиляторов, поведение в режиме отладки, т.д., т.п. При наличии времени и интереса это все, конечно, можно сделать.
Общие выводы:
1) Любой алгоритм, перенесенный чуть ли не один в один из 1С в компоненту, оживляется на порядок. С теми же родными типами данных платформы и вычисленным собственным же АПИ платформы.
2) Любой простенький алгоритм, написанный на языке 1С, несравнимо проигрывает вылизанному алгоритму из существующей С/С++ библиотеки, коих миллион.
Ну и в заключение, коль скоро у нас есть Делегат, можно похулиганить с полиморфизмом:
// Зоопарк делегатов
процедура ТестДелегатовЗоо() экспорт
перем мойЗоопарк;
мойЗоопарк =новый Массив;
мойЗоопарк.Добавить(СоздатьСтруктуруЖивотное_Кошка("Мурка"));
мойЗоопарк.Добавить(СоздатьСтруктуруЖивотное_Собака("Шарик"));
ТестДелегатовЗоо_ОпроситьЗоопарк(мойЗоопарк);
КонецПроцедуры
Функция СоздатьСтруктуруЖивотное_Собака(Имя)
возврат СоздатьСтруктуруЖивотное(Имя, СоздатьДелегат(МодТест,"ЖивотноеСобака_Голос"));
КонецФункции
Функция СоздатьСтруктуруЖивотное_Кошка(Имя)
возврат СоздатьСтруктуруЖивотное(Имя, СоздатьДелегат(МодТест,"ЖивотноеКошка_Голос"));
КонецФункции
Функция СоздатьСтруктуруЖивотное(Имя, Голос)
возвратновый ФиксированнаяСтруктура("Имя, Голос", Имя, Голос);
конецфункции
Функция ЖивотноеКошка_Голос() экспорт
возврат"Мяу";
КонецФункции
Функция ЖивотноеСобака_Голос() экспорт
возврат"Гав";
КонецФункции
процедура ТестДелегатовЗоо_ОпроситьЗоопарк(З)
перем Ж;
сообщить("*");
сообщить("*------- Перекличка в зоопарке делегатов -----------");
длякаждого Ж из З цикл
Сообщить("* Животное '" + Ж.Имя + "' отозвалось '" + Ж.Голос.Вызвать() + "'");
КонецЦикла;
сообщить("*------- ------------------------------- -----------");
сообщить("*");
КонецПроцедуры
*------- Перекличка в зоопарке делегатов -----------
* Животное 'Мурка' отозвалось 'Мяу'
* Животное 'Шарик' отозвалось 'Гав'
*------- ------------------------------- -----------
Все-таки структура с Делегатом это не совсем класс. Все равно опять .Вызвать() да .Выполнить(). Хочется полноценных интерфейсов, наследования, виртуальных методов и т.д. и т.п.
//Зоопарк классов
функция ПолучитьОписательКлассаЖивотное()
перем К;
К = СоздатьОписательКласса("КлассЖивотное");
К.ДобавитьПоле("Имя");
К.ДобавитьПоле("м_ВремяРождения");
К.ДобавитьМетод("ДайВозраст", МодТест,"КлассЖивотное_ДайВозраст");
К.ДобавитьАбстрактныйМетод("ДайГолос");
возврат К;
КонецФункции
функция ПолучитьОписательКлассаЖивотноеКошка()
перем К;
К = ПолучитьОписательКлассаЖивотное();
К.ПереопределитьМетод("ДайГолос", МодТест,"КлассЖивотноеКошка_Голос");
возврат К;
КонецФункции
функция ПолучитьОписательКлассаЖивотноеСобака()
перем К;
К = ПолучитьОписательКлассаЖивотное();
К.ПереопределитьМетод("ДайГолос", МодТест,"КлассЖивотноеСобака_Голос");
возврат К;
КонецФункции
функция КонструкторЖивотного(Класс, Имя, ВремяРождения)
Класс.Имя = Имя;
Класс.м_ВремяРождения = ВремяРождения;
возврат Класс;
КонецФункции
функция СоздатьКлассЖивотное_Собака(Имя)
//туду: возврат ПолучитьОписательКласса("ЖивотноеСобака").Конструкторы[..].Вызвать(<список-параметров>);
// => .Вызвать(<класс-интерфейфейс-конструктора>, <параметры>)
возврат КонструкторЖивотного(СоздатьКлассПоОписателю(ПолучитьОписательКлассаЖивотноеСобака()), Имя, ДайТики()-456);
КонецФункции
функция СоздатьКлассЖивотное_Кошка(Имя)
возврат КонструкторЖивотного(СоздатьКлассПоОписателю(ПолучитьОписательКлассаЖивотноеКошка()), Имя, ДайТики()-567);
КонецФункции
функция КлассЖивотное_ДайВозраст(Класс, Еденицы) экспорт
перем х;
х = ДайТики()- Класс.м_ВремяРождения;
возврат Окр(?(Еденицы = ТипЕдиницыШкалыВремени.Минута, х/60, х),1); //чё-то для теста
КонецФункции
функция КлассЖивотноеКошка_Голос(Класс) экспорт
возврат"Мур-Мяу";
КонецФункции
функция КлассЖивотноеСобака_Голос(Класс) экспорт
возврат"Гав-Гав";
КонецФункции
процедура ТестКлассовЗоо() экспорт
перем мойЗоопарк;
мойЗоопарк =новый Массив;
мойЗоопарк.Добавить(СоздатьКлассЖивотное_Кошка("Мурка"));
мойЗоопарк.Добавить(СоздатьКлассЖивотное_Собака("Шарик"));
ТестКлассовЗоо_ОпроситьЗоопарк(мойЗоопарк);
КонецПроцедуры
процедура ТестКлассовЗоо_ОпроситьЗоопарк(З)
перем Ж;
сообщить("*");
сообщить("*------- Перекличка в зоопарке классов -----------");
длякаждого Ж из З цикл
Сообщить("* Животное '"+ Ж.Имя +"', возраст = "+ Ж.ДайВозраст(ТипЕдиницыШкалыВремени.Минута)+", отозвалось '"+ Ж.ДайГолос()+"'");
КонецЦикла;
сообщить("*------- ----------------------------- -----------");
сообщить("*");
КонецПроцедуры
*------- Перекличка в зоопарке классов -----------
* Животное 'Мурка', возраст = 9,5, отозвалось 'Мур-Мяу'
* Животное 'Шарик', возраст = 7,9, отозвалось 'Гав-Гав'
*------- ----------------------------- -----------
Пока все. Сыро, но работает. Возможности развития ограничиваются только фантазией. Примененимость повсеместная. Назрело поговорить. Масса вопросов, например, по синтаксису:
- о множественном наследовании думать или как все, кроме плюсов?
- и вообще Класс он НАСЛЕДУЕТСЯ ОТ ?, РАСШИРЯЕТ ?
- class <name> : private <base-class> - такое надо?
- this, me, self- как? В текущей реализации это просто переменная. Пишу Класс, будут конфликты можно переименовать. Но все же...
- private - ЧАСТНЫЙ? ВНУТРЕННИЙ?
- mustoverride – ПЕРЕОПРЕДЕЛЯЕМЫЙ ОБЯЗАТЕЛЬНО?
- ...
И Особенно смущает вопрос, почему 1С это все скрывает, ведь все эти возможности есть у платформы с рождения.
У кого какие мысли?
Спасибо.
<необходимое дополнение>
Я добровольно обязуюсь не использовать имеющуюся у меня информацию о механизмах платформы 1С в целях несанкционированного доступа к данным, не публиковать такую информацию и обеспечить сохранность таковой информации от несанкционированного доступа третьих лиц.