Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.140.186.241] |
|
Сообщ.
#1
,
|
|
|
Всем привет, особенно Jin X (с целью подмазаться) !
В уиндовс так не хватает линуксовой команды 'time' ... вот я и подумал, а почему бы ее не запилить на ассемблере?! Но, увы, столько времени утекло - где я, и где ассемблер Поэтому просьба, как в том фильме "вельми понеже, паки-паки, иже херувимы" (С) поделитесь наработками, плс! Нужно: * считать argv[1] * запустить таймер высокого разрешения * запустить argv[1] как процесс * остановить таймер высокого разрешения * вернуть или затраченное время процессом, или сообщение об ошибке запуска Почему на асме? С++ компилеры выдают жЫрноту, меня это бесит - тут на максмимум 3кб размер исполняемого файта. Ассемблер интересен. Можно запилить на голом Си+плюшки (используя к примеру LCC) ... но это уже будет другая песня не об этом. И еще ... если такая возможность есть - не линковать CRT.DLL. Чем "автономней исполняемый модуль - тем лучше, и пофик на лишний килобайт. |
Сообщ.
#2
,
|
|
|
Цитата JoeUser @ Есть QueryPerformanceCounter и QueryPerformanceFrequency.В уиндовс так не хватает линуксовой команды 'time' Т.е. читаем QueryPerformanceCounter до запуска, QueryPerformanceCounter после запуска, считаем разницу и делим на QueryPerformanceFrequency, получаем секунды. Запуск – CreateProcess. Вывести можно, если писать на MASM32 через функции встроенной библиотеки: StdOut, dw2a (или wsprintf – это не msvcrt). А чем, собственно, msvcrt не устраивает? Проще всего ж использовать printf. Но не знаю, насколько такой таймер реально оправдан, ведь на запуск тоже уйдёт какое-то время и внесёт свою погрешность, так что, может, GetTickCount достаточно? |
Сообщ.
#3
,
|
|
|
цыгвин
|
Сообщ.
#4
,
|
|
|
Jin X, пасип ... я позже зайду!
|
Сообщ.
#5
,
|
|
|
Цитата Gonarh @ Иван Иваныч? цыгвин |
Сообщ.
#6
,
|
|
|
Цитата Gonarh @ цыгвин Cygwin? Ай донт андерстенд! |
Сообщ.
#7
,
|
|
|
Цитата JoeUser @ Cygwin? Яя, натюрлих. |
Сообщ.
#8
,
|
|
|
Цитата JoeUser @ Гм, "жЫрнота" будет везде:Почему на асме? С++ компилеры выдают жЫрноту, меня это бесит - тут на максмимум 3кб размер исполняемого файта. С++, писано за 10 минут, один сеанс отладки, размер образа от Студии 214к #include <windows.h> # #include <chrono> #include <iostream> #include <vector> #include <string> int main(int argn, char *argv[]) { PROCESS_INFORMATION procInfo; STARTUPINFO startInfo= { sizeof(startInfo), NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, STARTF_FORCEOFFFEEDBACK, 0, 0, NULL, NULL, NULL, NULL}; std::string cmdLine; for (int i = 1; i < argn; ++i) cmdLine.append(argv[i]) += ' '; if (cmdLine.empty()) return std::cout << "App for run is absent\n", 1; std::vector<char> appName(std::begin(cmdLine), std::end(cmdLine)); appName[appName.size()-1] = '\0'; auto start = std::chrono::system_clock::now(); if (CreateProcess(NULL, &appName.front(), NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL, &startInfo, &procInfo) == 0) return std::cout << "Error of " << argv[1] << " launch. Error code is " << GetLastError() << '\n', 1; else { CloseHandle(procInfo.hThread); WaitForSingleObject(procInfo.hProcess, INFINITE); auto now = std::chrono::system_clock::now(); CloseHandle(procInfo.hProcess); std::cout << "Time of run is " << std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count() << " ms.\n"; } return 0; } С, писано за 35 минут, 6 сеансов отладки, размер образа от Студии 100к #include <windows.h> # #include <malloc.h> #include <string.h> #include <stdio.h> int append(char **dst, const char *src) { size_t srcLen = strlen( src), dstLen = strlen(*dst); char *newDst =realloc(*dst, dstLen + srcLen + 1); if (newDst == NULL) return 0; *dst = newDst; strcat(*dst, src)[dstLen + srcLen] = ' '; (*dst)[dstLen + srcLen + 1] = '\0'; return 1; } int main(int argn, char *argv[]) { PROCESS_INFORMATION procInfo = { NULL }; STARTUPINFO startInfo= { sizeof(startInfo), NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, STARTF_FORCEOFFFEEDBACK, 0, 0, NULL, NULL, NULL, NULL}; char *cmdLine = calloc(1, 1); int res = 1, i; if (cmdLine == NULL) { printf("Insufficient memory\n"); goto cleanup; } for (i = 1; i < argn; ++i) if (!append(&cmdLine, argv[i])) { printf("Insufficient memory\n"); goto cleanup; } if (cmdLine[0] == '\0') { printf("App for run is absent\n"); goto cleanup; } cmdLine[strlen(cmdLine)-1] = '\0'; LARGE_INTEGER start, now, freq; if (QueryPerformanceFrequency(&freq) == 0) { printf("Time service is unavailable. Error code is %u\n", GetLastError()); goto cleanup; } if (QueryPerformanceCounter(&start) == 0) { printf("Fault of time service request for start. Error code is %u\n", GetLastError()); goto cleanup; } if (CreateProcess(NULL, cmdLine, NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL, &startInfo, &procInfo) == 0) { printf("Error of %s launch. Error code is %u\n", argv[1], GetLastError()); goto cleanup; } CloseHandle(procInfo.hThread); WaitForSingleObject(procInfo.hProcess, INFINITE); if (QueryPerformanceCounter(&now) == 0) { printf("Fault of time service request for finish. Error code is %u\n", GetLastError()); goto cleanup; } printf("Time of run is %llu ms\n", (now.QuadPart - start.QuadPart) * 1000 / freq.QuadPart); res = 0; cleanup: if (cmdLine != NULL) free(cmdLine); if (procInfo.hProcess != NULL) CloseHandle(procInfo.hProcess); return res; } Для 3-х кб нужно выключить стандартные библиотеки и юзать только WinAPI. Последний вариант несложно адаптировать, просто умножаем время и отладку ещё на два. P.S. wsptintf() не умеет вывод long long. WinAPI, поиск сырцов в crt\srс\i386 и "портирование" за полчаса, четыре сеанса отладки, размер образа от Студии 3,5к #include <windows.h> # #include <stdarg.h> #define OUT_BUFFER_SIZE 1024 int wprintf(LPCTSTR fmt, ...) { char out[OUT_BUFFER_SIZE]; va_list args; int written; va_start(args, fmt); written = wvsprintf(out, fmt, args); va_end(args); WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), out, written, &written, NULL); return written; } const char *getCmdLine(const char *cmd) { if (*cmd == '"') while(*++cmd != '"') ; while (*cmd != ' ') ++cmd; while (*cmd == ' ') ++cmd; return cmd; } int entryPoint(void) { PROCESS_INFORMATION procInfo = { NULL }; STARTUPINFO startInfo= { sizeof(startInfo), NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, STARTF_FORCEOFFFEEDBACK, 0, 0, NULL, NULL, NULL, NULL}; const char *commandLine = getCmdLine(GetCommandLine()); int res = 1, i; char *cmdLine; if (*commandLine == '\0') { wprintf("App for run is absent\n"); goto cleanup; } cmdLine = HeapAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, lstrlen(commandLine)+1); if (cmdLine == NULL) { wprintf("App for run is absent\n"); goto cleanup; } lstrcpy(cmdLine, commandLine); LARGE_INTEGER start, now, freq; if (QueryPerformanceFrequency(&freq) == 0) { wprintf("Time service is unavailable. Error code is %u\n", GetLastError()); goto cleanup; } if (QueryPerformanceCounter(&start) == 0) { wprintf("Fault of time service request for start. Error code is %u\n", GetLastError()); goto cleanup; } if (CreateProcess(NULL, cmdLine, NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL, &startInfo, &procInfo) == 0) { wprintf("Error of %hs launch. Error code is %u\n", cmdLine, GetLastError()); goto cleanup; } CloseHandle(procInfo.hThread); WaitForSingleObject(procInfo.hProcess, INFINITE); if (QueryPerformanceCounter(&now) == 0) { wprintf("Fault of time service request for finish. Error code is %u\n", GetLastError()); goto cleanup; } wprintf("Time of run is %lu ms\n", (unsigned long)((now.QuadPart - start.QuadPart) * 1000 / freq.QuadPart)); res = 0; cleanup: if (cmdLine != NULL) HeapFree(GetProcessHeap(), HEAP_NO_SERIALIZE, cmdLine); if (procInfo.hProcess != NULL) CloseHandle(procInfo.hProcess); return res; } Строки для компиляции и линковки: ml -c lldiv.asm llmul.asm cl -O1 -GL -Zl -GS- сырец.c lldiv.obj llmul.obj user32.lib kernel32.lib -link /NODEFAULTLIB /ENTRY:entryPoint /SUBSYSTEM:CONSOLE Цитата JoeUser @ Это совсем другой вопрос. P.S. Последний сырец к услугам. Ассемблер интересен. |
Сообщ.
#9
,
|
|
|
Qraizer, а почему system_clock, а не high_resolution_clock?
|
Сообщ.
#10
,
|
|
|
Та без разницы. Миллисекунды же. Если миллисекунд вдруг мало, то да.
|
Сообщ.
#11
,
|
|
|
Цитата Qraizer @ Оно того точно стоило? Да! Вот это красиво, если оно так (пока не проверил)!!! И утиль полезный, и реализация на достойном уровне. Пасип! ЗЫ: Но с с асмом я всеж позже разберусь - хотелось увидеть куски кода для этих под-задач. Наверняка они привычны и злободневны для асм-писателей. |
Сообщ.
#12
,
|
|
|
Qraizer, кстати ... а ты когда-нить смотрел в сторону lcc?
Описалово и линки, дистр x32, дистр x64, есличо. Добавлено Цитата Jin X @ Т.е. читаем QueryPerformanceCounter до запуска, QueryPerformanceCounter после запуска, считаем разницу и делим на QueryPerformanceFrequency, получаем секунды. Ну пока, "на коленке", я вот такое намутил: @echo off SET RUN="ffmpeg -f rawvideo -pix_fmt yuv420p -s:v 1152x864 -r 25 -i out.yuv -i out.mp3 -c:v libx265 -preset veryfast -c:a libvorbis -shortest res.mkv" PowerShell -NoProfile -ExecutionPolicy Bypass Measure-Command {%RUN%} > test-1-res.txt Но нормальную утилиту time я уже вчера хочу! Добавлено Цитата JoeUser @ Qraizer, кстати ... а ты когда-нить смотрел в сторону lcc? И еще вот это ... что скажешь с точки зрения С++ профи? Сорри за оффтоп - просто уж больно интересная шляпа!!!!! Очень хочется с "иглы Qt" соскочить Добавлено Скрытый текст Цитата Qraizer @ Оно того точно стоило? И есчо !!! Если приаттачить lldiv.asm llmul.asm, то твои труды неплохо было бы в FAQ C++ выложить. Мыже исходники, или где?! |
Сообщ.
#13
,
|
|
|
Цитата Qraizer @ Так, человек же даже в заголовке написал: "[Т]аймер высокого разрешения" Та без разницы. Миллисекунды же. Если миллисекунд вдруг мало, то да. |
Сообщ.
#14
,
|
|
|
А чем миллисекунды не высокое, Jin X? Цифры не указаны, и я выбрал миллисекунды из практических соображений вокруг формулировки задачи. Ну и если придираться, нужно ещё убедиться, что QueryPerformanceFrequency() вернёт подходящие цифры. Впрочем, и решить задачу задачи не ставил, иначе на ассме б выложил. А так помог, на сколько было не жалко времени, перевести последний вариант на асм – вопрос желания, а не усилий.
Нет, не смотрел, JoeUser. На что там стоит смотреть? Ну компилятор и компилятор, чем он примечателен, чтоб на него обратить внимание? Ну, кроме лицензии, которую нынче и VS покрывает. За nano слышал, хвалили, не юзал. Цитата JoeUser @ Это стандартные файлики из поставки сырцов RTL в VS. Компилятор их использует для умножения и деления 64-битных целых. Так-то они лежат в либах, но т.к. те отключены, нужно подкинуть свою реализацию. Мне было лень, я компильнул оригиналы. И нужны они только для 32-битного кода, в 64-х битах компилятор сам справляется с 64-битной арифметкой. Если приаттачить lldiv.asm llmul.asm, то твои труды неплохо было бы в FAQ C++ выложить. |
Сообщ.
#15
,
|
|
|
Цитата Qraizer @ Вообще, я согласен, что при запуске внешней проги, считать точнее, чем в миллисекундах, нет особого смысла.и я выбрал миллисекунды из практических соображений вокруг формулировки задачи Цитата Jin X @ Это про микро/наносекунды. не знаю, насколько такой таймер реально оправдан, ведь на запуск тоже уйдёт какое-то время и внесёт свою погрешность, так что, может, GetTickCount достаточно? |
Сообщ.
#16
,
|
|
|
Запилил-таки на fasm'е, за одно и узнал как я отстал от жысти!
Размер экзешника получился прекрасный - 3072 байта format PE console entry start ; ***************************************************************************** ; timer.exe - Утилита замера времени исполнения программы, задаваемой в ; коммандной строке. fasm 1.73.22 ; ; Copyleft (L) 2020 Majestio AKA JoeUser = http://majestio.info ; ; ***************************************************************************** ; Если командная строка содержит пробелы - ее записываем в кавычках! ; ; ***************************************************************************** include 'win32wx.inc' ;======================================= section '.data' data readable writeable ;======================================= argstr TCHAR 13,10,'Launch: "%s" ... ',13,10,13,10,0 usagestr TCHAR 13,10,'Usage: timer.exe <"cmd">',13,10,0 okstr TCHAR 13,10,'Done : %.5f sec',13,10,0 erstr TCHAR 13,10,'Error (%d)!',13,10,0 ;======================================= section '.bss' data readable writeable ;======================================= SystemInfo STARTUPINFO ProcessInfo PROCESS_INFORMATION argc dd 0 ; количество аргументов командной строки argv dd 0 ; ссылка на cnt1 dq 0 ; счетчик начала cnt2 dq 0 ; счетчик завершения freq dq 0 ; делитель частоты ;======================================= section '.code' code readable executable ;======================================= start: invoke GetCommandLineW invoke CommandLineToArgvW, eax, addr argc mov [argv], eax ; проверяем, что аргумент ровно один ARGV[1] (ARGV[0] есть всегда) mov eax,[argc] cmp eax,2 jne usage ; заносим в argv - ARGV[1] mov eax, [argv] mov eax, [eax+4] mov [argv], eax ; Печатаем название запускаемой программы invoke wprintf, argstr, [argv] mov [SystemInfo.cb],sizeof.STARTUPINFO invoke GetStartupInfoW, SystemInfo or [SystemInfo.dwFlags],STARTF_USESHOWWINDOW mov [SystemInfo.wShowWindow],SW_HIDE ; Чекаем таймер invoke QueryPerformanceFrequency, addr freq test eax, eax jnz @f mov eax, 1; Error 1 jmp error @@: invoke QueryPerformanceCounter, addr cnt1 test eax, eax jnz @f mov eax, 2; Error 2 jmp error ; Запускаем процесс @@: invoke CreateProcess,0,[argv],NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS,NULL,NULL,SystemInfo,ProcessInfo test eax,eax jnz @f mov eax, 3; Error 3 jmp error ; ждем завершения процесса @@: invoke WaitForSingleObject, [ProcessInfo.hProcess], -1 cmp eax, 0xFFFFFFFF jne @f mov eax, 4; Error 4 jmp error ; Закрываем хендлы @@: invoke CloseHandle, [ProcessInfo.hProcess] test eax,eax jnz @f mov eax, 5; Error 5 jmp error @@: invoke CloseHandle, [ProcessInfo.hThread] test eax,eax jnz @f mov eax, 6; Error 6 jmp error ; Чекаем таймер @@: invoke QueryPerformanceCounter, addr cnt2 test eax,eax jnz @f mov eax, 7; Error 7 jmp error ; Считаем время @@: finit fild qword [cnt2] fild qword [cnt1] fsubp fild qword [freq] fdivp fstp qword [cnt1] ; Выводим время в секундах в виде double:qw invoke wprintf, okstr, dword[cnt1], dword[cnt1+4] jmp exit usage: invoke wprintf, usagestr jmp exit error: invoke wprintf, erstr, eax exit: invoke ExitProcess, 0 ;==================================== section '.idata' import data readable ;==================================== library kernel,'kernel32.dll',\ shell32,'shell32.dll',\ msvcrt,'msvcrt.dll' import kernel,\ QueryPerformanceFrequency,'QueryPerformanceFrequency',\ QueryPerformanceCounter,'QueryPerformanceCounter',\ CloseHandle,'CloseHandle',\ GetStartupInfoW,'GetStartupInfoW',\ WaitForSingleObject,'WaitForSingleObject',\ ExitProcess,'ExitProcess',\ CreateProcess,'CreateProcessW',\ GetCommandLineW,'GetCommandLineW' import shell32,\ CommandLineToArgvW,'CommandLineToArgvW' import msvcrt,\ wprintf,'wprintf' Цитата Qraizer @ P.S. wsptintf() не умеет вывод long long Зато wprintf выводит прекрасно Я этим в коде, кстати, времечко и вывожу из double. |
Сообщ.
#17
,
|
|
|
Цитата JoeUser @ wprintf не WinAPI, это RTL. Если же в RTLless тащить плавающую точку, линковать с стандартными символами, которые я же и отключил, ещё сложнее, чем с llmul и lldiv Зато wprintf выводит прекрасно Я этим в коде, кстати, времечко и выводу из double. Добавлено Цитата JoeUser @ Я бы перетащил первую перед WaitForSingleObject(), вторую после QueryPerformanceCounter(). Чуть точнее будет. ...CloseHandle... |
Сообщ.
#18
,
|
|
|
Ну сидит оно в msvcrt.dll и сидит себе - эта дээлелька дефакто всегда в системе. Не?
|
Сообщ.
#19
,
|
|
|
Плюс ты не учитываешь возможные параметры для запускаемого процесса. Представь, что тебе надо замерить время для %comspec% / c call BuildCMakeScript.bat
Добавлено Цитата JoeUser @ Не помню. Но коли на динамическую RTL завязываться, то и на C++ можно получить ~15Кб. Ну сидит оно в msvcrt.dll и сидит себе - эта дээлелька дефакто всегда в системе. Не? |
Сообщ.
#20
,
|
|
|
Цитата Qraizer @ Чуть точнее будет. Думал над этим, но так захотелось сперва хэндлы позакрывать - прямо кушать не могу! Добавлено Цитата Qraizer @ %comspec% / c call BuildCMakeScrupt.bat Не не не ... я спецом эту муть сразу выкинул! Есть там функция раскрытия окружения ... знаю, так можно еще и начать в проге собирать все ARGV в одну строку, парсить, через жэ со знаками пунктуации сражаться. Для удобства придумали командные файлы! Хочешь какой-то пакет программ затестить - пиши батник потом его "cmd /c batbatbat.cmd", и все . А уж в батнике что хошь "резольвь". Моя задача была написать оч маленький и шустрый утиль. Это все. |
Сообщ.
#21
,
|
|
|
Та дело не %comspec% же. Ну пиши своё cmd /c batbatbat.cmd, всё равно ж у тебя параметры-то.
|
Сообщ.
#22
,
|
|
|
Запускаю - timer.exe "cmd /C package.bat" ... Все запускается, все чекается. Просто проверь, если ты командную строку обрамишь двойными кавычками, то она вся полностью залезет в ARGV[1], а не будет дробиться. Вот это и пользую.
|
Сообщ.
#23
,
|
|
|
Ну, дело хозяйское. Но как по мне, если можно избавиться от человеческого фактора легко и просто посредством
const char *getCmdLine(const char *cmd) { if (*cmd == '"') while(*++cmd != '"') ; while (*cmd != ' ') ++cmd; while (*cmd == ' ') ++cmd; return cmd; } /* ... */ const char *commandLine = getCmdLine(GetCommandLine()); Добавлено Ну и по-хорошему после CommandLineToArgvW() нужен LocalFree() |
Сообщ.
#24
,
|
|
|
Цитата Qraizer @ Ну и по-хорошему после CommandLineToArgvW() нужен LocalFree() Да, не дочитал хелп, позже исправлю. Добавлено Цитата Qraizer @ Но как по мне, если можно избавиться от человеческого фактора легко и просто посредством Ага ... а потом еще ExpandEnvironmentStringsW добавить, а потом ... встроенную html-справку Своих зайцев я убил: чем камни гонять и считать бэйнчи - у меня есть, до асма добрался и оттопырился ... на время хоть от этого Qt отвлекся. |
Сообщ.
#25
,
|
|
|
Цитата JoeUser @ Иконку с текущими секундами в трей, менюху по правому клику с About и Break, автоапдейт с https... Придумать много можно, только это несерьёзно. А человеческий фактор серьёзно, у меня бы этот код ревью не прошёл. Ага ... а потом еще ExpandEnvironmentStringsW добавить, а потом ... встроенную html-справку |
Сообщ.
#26
,
|
|
|
Цитата Qraizer @ легко и просто посредством Хороший код, но от кривожопов на 100% не спасает! Если не кавычка, так пробел ... демо |
Сообщ.
#27
,
|
|
|
Qraizer. хотя, если честно с ExpandEnvironmentStringsW я может быть бы еще согласился ... изредка бывает нужно вспомнить где этот %CARGO_HOME%, к примеру. Раз в месяц Но я пошел в пляс от Бритвы Оккамы - сделал то, что нужно сегодня мне, самому хорошему и скромному человеку в солнечной системе. Для усталых, невнимательных, тупых, слепых - могу сделать завтра, только оплатите мне абонемент в бассейн на месяц с девушками!
|
Сообщ.
#28
,
|
|
|
Цитата Qraizer @ Ну и по-хорошему после CommandLineToArgvW() нужен LocalFree() Хотя ... не буду. Формально - ты прав! Но, в моем случае, это происходит один раз - и утечка электронного мозга, как таковая, нивилируется просто завершением процесса. Но, на заметочку себе оставлю. Сообщения были разделены в тему "Измерение времени выполнения кода" |
Сообщ.
#29
,
|
|
|
Цитата Jin X @ чем в миллисекундах, нет особого смысла Отчасти ты прав. Но многие наверное забыли мат.статистику ... Простой пример. У нас есть эмпирическая выборка из 1000 величин. По сути - выборка: 1) Означает какое-то влияние 2) У этого влияния есть какой-то коэффициент 3) У эмпирически собранных данных есть разброс - это де факто Но есть еще и Ситуация Йопта) когда по какой-то причине сбиваются вычисления/отсчеты. Напомню, как поступают бородатые дядьки... в плане их собственной "нормализации" 1) Все величины сортирют 2) Начинают отсекать "экстремумы". Для этого делят весь отрезок на 4 части. Из частей 2-3 получают либо среднне, либо средне-квадратичное 3) Из отрезков 1 и 4 начинают удалять значения, которые по модулю разности меньше вычлененных значений и удаляемых более, чем в два раза 4) Если число усечений справа и слева не превышает 20-25% - получают верный коэффициент влияния 5) Если не получилось - или повторяют, или утверждают, что эксперимент носит вааще никакую детерминированность (типа искать не тут, давайде другие данные) Спрашивается а при чем тут "милисекунды"? А они просто - часть эксперимента. Запустив 1000 раз одно и то же здание в реальных условиях (загружжености компа, незагруженности) - мы выйдем к "СРЕДНЕМУ". По методе, как я описал выше. Коэффициент какойта! Как назвать, сами считайте - скорости, мощности, офигительности ... |