Да... Команда нужная! т.е. поменять Mov Al, 'v' на Mov Al, 10 (после конца предыдущей - начало следующей). Непонятно почему, несмотря на Вы приводите массивные примеры "Case"- программирования "If"-ами? Вместо: Код (ASM): Mov eSi, Что разобрать Mov eCx, Сколько максимально - ; Т.Е. длина файла eCx и ([eSi+eCx]=0=EoF) должен находиться ; "вне" файла и рассмотрение его избыточно ! ClD ; / StD - направление xOr eAx, eAx LEA eBx, [BitMap1] LEA eDx, [CallTab1] ; или [JmpTab1] _1: LodsB ; например BT [eBx], eAx ;; JnC _2 ; возможно ускоряет, если мало токенов в обработке Call [eDx + eAx * 4] ; если надо почитабельнее ; Jmp [eDx + eAx * 4] ; как в вашем случае с "возвратом" Jmp _2 _2: LoopNZd _1 естественно при работе внутри цикла (_1 eBx, eDx и даже eSi c eCx могут меняться для следующих этапов "дерева" разбора! Простой пример: попался комментарий '#' Код (ASM): _NextLine Proc ; для Call [eDx + eAx * 4] Mov eDi, eSi Mov Al, 10 ; конец строки RepNE ScasB Mov eSi, eDi Test eCx, eCx Ret _NextLine EndP Возникает простой вопрос - может ли быть более 1-го токена в строке? Если не может, то после него... до конца строки включительно рассматривать нечего, в вами приведенном коде, и т.д. и т.п. Я пока не планирую процессор мощнее (E8400 у меня), но интересно, ~во сколько VPCMPISTRI быстрее ее аналога выше? P.S. От ошибок никто не застрахован.((( Либо 13,10, либо 0Dh, 0Ah.
Сначала по другому было: Код (ASM): Mov eSi, Что разобрать Mov eCx, Сколько максимально - ; Т.Е. длина файла eCx и ([eSi+eCx]=0=EoF) должен находиться ; "вне" файла и рассмотрение его избыточно ! ClD ; / StD - направление xOr eAx, eAx LEA eDx, [CallTab1] ; или [JmpTab1] _1: LodsB ; например Call [eDx + eAx * 4] ; если надо почитабельнее ; Jmp [eDx + eAx * 4] ; как в вашем случае с "возвратом" на _2 ; в JmpTab1 dd _2 ; для необрабатывемых _2: LoopD _1 _NextLine Proc ; для Call [eDx + eAx * 4] Mov eDi, eSi Mov Al, 10 ; конец строки RepNE ScasB Mov eSi, eDi Cmp eCx, 1 ;; AdC eCx, 0 ; так надежнее при возврате на _2: Ret _NextLine EndP потом подумал улучшить... и в конце - 1-ну инструкцию "пожалел" и ошибся - может неконролируемо вывалиться: Код (ASM): .... xOr eAx, eAx ; ZF=1 в самом начале по JnC _2 .... JnC _2 .... _2: LoopNZd _1 ; при уходе на ..... _Ret Proc ; для Call [eDx + eAx * 4] Ret _Ret EndP .... ;или в при JmpTab1 dd _2 ; для необрабатывемых P.S. У меня сейчас есть задачи обработки строк методом проб и ошибок))) поэтому возможно даже излишне активен в теме.
Можно сделать надежно(элитный способ) для подгрузки сторонних проектов(если не известно какое будет форматирование/мусорные токены), но при этом будет больше вычислений, сам код при этом будет гораздо лаконичнее. Либо 2 варианта загрузки(я так обычно не делаю): быстрый(обработка переносов/мусорных токенов - как у вас) и медленный(максимально надежный, но там немного другая логика).
А в этом и особенность низкоуровневого подхода. Когда я пишу на ассемблере, я не думаю о конструкциях Select Case, While или If. Это касается и организации главного цикла, и обработчика сообщений, и парсера. Когда я обрабатываю сообщения, я просто пробегаю по условиям в порядке их вероятности и/или критичности: Код (ASM): ;Messages Arranged by Probability cmp edx,111h je lbl_wmCommand ;Самое ожидаемое cmp edx,5 je lbl_wmSize ;Приходит несколько раз cmp edx,10h je lbl_wmClose ;Один либо несколько раз, если юзер передумает cmp edx,2 je lbl_wmDestroy ;Ждём единственного случая cmp edx,1 je lbl_wmCreate ;Один раз в начале, дальше - балласт ;None of the Above call DefWindowProcA Аналогично в парсере: Код (ASM): ;Token detection. Arranged by Probability ;mov rcx,pCurrentPosition cmp byte ptr[rcx],76h ;"v" - Вершин будут десятки тысяч je lbl_TokenStartsWithV cmp byte ptr[rcx],66h ;"f" - Полигонов будут тысячи je lbl_TokenFace cmp byte ptr[rcx],67h ;"g" - Несколько десятков групп je lbl_TokenGroup cmp dword ptr[rcx],6D657375h ;"usem" in reverse order - part of "usemtl" - Материалов наберётся с десяток je lbl_TokenUseMtl cmp byte ptr[rcx],6Fh ;"o" - Несколько объектов je lbl_TokenObject cmp dword ptr[rcx],6C6C746Dh; "mtll" in reverse order - part of "mtllib" - Одна или пара библиотек je lbl_TokenMtlLib jmp lbl_SkipByte Но: до разбора токенов надо ещё добраться! Допустим, я на первом проходе считаю количество токенов, чтобы узнать размер выделяемой памяти. Вот типичная строка: f -97/-112/-128 -81/-88/-104 -82/-89/-105 Я увидел токен f и увеличил inc gnTotalFaces. После этого остаток строки мне не нужен. Теперь мне нужно искать 13h,10h либо EoF. Я не буду гонять через память каждый байт инструкциями lodsb/cmpsb, я лучше загружу сразу 16 байт и проверю их внутри CPU. Вопрос по существу. Вразумительных отчётов я не видел (хотя и не искал особо), мне сейчас интереснее проверить на своём опыте. Очень может быть, что Вы правы, и для перебора строк лучше побайтовые команды. Но у меня сейчас такое приложение, в котором мозги уже переключились на векторный подход, поэтому хочется выдержать чистоту жанра.
А я пока собрал то, о чём говорил: Код (ASM): countObjEntities proc PROLOG 100h ;Initialize Counters mov gnTotalObjects,0 mov gnTotalGroups,0 mov gnTotalSubGroups,0 mov gnTotalFaces,0 mov gnTotalVertices,0 mov gnTotalNormals,0 mov gnTotalTextureVectors,0 mov gnTotalMtlLibs,0 ;Set the Local Pointer mov rcx,gpObjDataStart cmp rcx,gpObjDataEnd jge lbl_EndOfFile lbl_ReadNextLine: mov rsi,rcx ;pCurrentPosition ;Do not use rcx to store pCurrentPosition ;because it is used by vpcmpistri as index lbl_NextXmmword_0: cmp rsi,gpObjDataEnd jge lbl_EndOfFile ;Pattern: ASCII code: 'm', '#', 's', 'u', 'o', 'g', 'f', 'v' mov rax,6D2373756F676676h ;m#suogfv movq xmm0,rax ;Compare bytes at [rsi] against pattern in XMM0 ;Mode 0: equal_any, positive polarity, implicit length vpcmpistri xmm0,xmmword ptr[rsi],0 jz lbl_EndOfFile jc lbl_TokenFound add rsi,10h ;Compute the next address to check jmp lbl_NextXmmword_0 lbl_TokenFound: ;ECX contains the index of the first found character add rcx,rsi ;Compute the absolute address cmp rcx,gpObjDataEnd jge lbl_EndOfFile ;Token detection. Arranged by Probability cmp byte ptr[rcx],76h ;"v" je lbl_BranchV cmp byte ptr[rcx],66h ;"f" je lbl_BranchF cmp byte ptr[rcx],67h ;"g" je lbl_BranchG cmp byte ptr[rcx],6Fh ;"o" je lbl_BranchO cmp byte ptr[rcx],75h ;"u" je lbl_BranchU cmp byte ptr[rcx],73h ;"s" je lbl_BranchS cmp byte ptr[rcx],23h ;"#" je lbl_BranchComment cmp byte ptr[rcx],6Dh; "m" je lbl_BranchM jmp lbl_FindNextLine ;Branch "v" lbl_BranchV: inc rcx ;Check what's next to "v" cmp byte ptr[rcx],20h ;Space after "v" - Most probable je lbl_TokenVertex cmp byte ptr[rcx],6Eh ;"n" after "v" je lbl_TokenVN cmp byte ptr[rcx],74h ;"t" after "v" je lbl_TokenVT cmp byte ptr[rcx],9 ;Tab after "v" - Least probable je lbl_TokenVertex jmp lbl_FindNextLine ;Count vertex: v x y z lbl_TokenVertex: inc gnTotalVertices jmp lbl_FindNextLine ;Branch "vn" lbl_BranchVN: inc rcx ;Check what's next to "vn" - Most probable cmp byte ptr[rcx],20h ;Space after "vn" je lbl_TokenNormal cmp byte ptr[rcx],9 ;Tab after "vn" - Least probable je lbl_TokenNormal jmp lbl_FindNextLine ;Junk ;Count normal: vn x y z lbl_TokenNormal: inc gnTotalNormals jmp lbl_FindNextLine ;Branch "vt" lbl_BranchVT: inc rcx ;Check what's next to "vt" cmp byte ptr[rcx],20h ;Space after "vt" - Most probable je lbl_TokenTextureVector cmp byte ptr[rcx],9 ;Tab after "vt" - Least probable je lbl_TokenTextureVector jmp lbl_FindNextLine ;Junk ;Count texture vector: vt u v w lbl_TokenTextureVector: inc gnTotalTextureVectors jmp lbl_FindNextLine ;Branch "f" lbl_BranchF: inc rcx ;Check what's next to "f" cmp byte ptr[rcx],20h ;Space after "f" - Most probable je lbl_TokenFace cmp byte ptr[rcx],9 ;Tab after "f" - Least probable je lbl_TokenFace jmp lbl_FindNextLine ;Junk ;Count face: f v/vt/vn v/vt/vn v/vt/vn lbl_TokenFace: inc gnTotalFaces jmp lbl_FindNextLine ;Branch "g" lbl_BranchG: inc rcx ;Check what's next to "g" cmp byte ptr[rcx],20h ;Space after "g" - Most probable je lbl_TokenGroup cmp byte ptr[rcx],9 ;Tab after "g" - Least probable je lbl_TokenGroup jmp lbl_FindNextLine ;Junk ;Count object name: g name lbl_TokenGroup: inc gnTotalGroups jmp lbl_FindNextLine ;Branch "o" lbl_BranchO: inc rcx ;Check what's next to "o" cmp byte ptr[rcx],20h ;Space after "o" - Most probable je lbl_TokenObject cmp byte ptr[rcx],9 ;Tab after "o" - Least probable je lbl_TokenObject jmp lbl_FindNextLine ;Junk ;Count object name: o name lbl_TokenObject: inc gnTotalObjects jmp lbl_FindNextLine ;Branch "U" lbl_BranchU: mov rax,qword ptr[rcx] ;"??ltmesu" - "usemtl" and two unknown bytes in reverse order shl rax,10h ;Zero out two latter bytes mov rbx,6c746D6573750000h ;"usemtl" and two zeroes in reverse order test rax,rbx jnz lbl_FindNextLine ;Junk add rcx,6 ;Skip Token cmp byte ptr[rcx],20h ;Space after "mtllib" - Most probable je lbl_TokenUseMtl cmp byte ptr[rcx],9 ;Tab after "mtllib" - Least probable je lbl_TokenUseMtl jmp lbl_FindNextLine ;Junk ;Count SubGroup: "usemtl" lbl_TokenUseMtl: inc gnTotalSubGroups add rcx,6 ;Skip Token jmp lbl_FindNextLine ;Branch "s" lbl_BranchS: inc rcx ;Check what's next to "s" ;cmp byte ptr[rcx],20h ;Space after "o" - Most probable ;je lbl_TokenS ;cmp byte ptr[rcx],9 ;Tab after "o" - Least probable ;je lbl_TokenS jmp lbl_FindNextLine ;Skip temporarily ;Branch "#" lbl_BranchComment: inc rcx ;Check what's next to "s" ;cmp byte ptr[rcx],20h ;Space after "#" - Most probable ;je lbl_TokenS ;cmp byte ptr[rcx],9 ;Tab after "#" - Least probable ;je lbl_TokenS jmp lbl_FindNextLine ;Skip temporarily ;Branch "m" lbl_BranchM: mov rax,qword ptr[rcx] ;"??billtm" - "mtllib" and two unknown bytes in reverse order shl rax,10h ;Zero out two latter bytes mov rbx,62696c6c746D0000h ;"mtllib" and two zeroes in reverse order test rax,rbx jnz lbl_FindNextLine ;Junk add rcx,6 ;Skip Token cmp byte ptr[rcx],20h ;Space after "mtllib" - Most probable je lbl_TokenMtlLib cmp byte ptr[rcx],9 ;Tab after "mtllib" - Least probable je lbl_TokenMtlLib jmp lbl_FindNextLine ;Junk ;Count material: "mtllib" lbl_TokenMtlLib: inc gnTotalMtlLibs add rcx,6 ;Skip Token jmp lbl_FindNextLine lbl_FindNextLine: inc rcx ;Skip previously checked byte mov rsi,rcx ;pCurrentPosition ;Do not use rcx to store pCurrentPosition ;because it is used by vpcmpistri as index lbl_NextXmmword_1: cmp rsi,gpObjDataEnd jge lbl_EndOfFile ;Pattern: ASCII code: Carriage Return = 0Dh, Line Feed = 0Ah mov rax,0Ah ;Chech for LF alone movq xmm0,rax ;Compare bytes at [rsi] against pattern in XMM0 ;Mode 0: equal_any, positive polarity, implicit length vpcmpistri xmm0,xmmword ptr[rsi],0 jz lbl_EndOfFile jc lbl_EndOfLineFound add rsi,10h ;Compute the next address to check jmp lbl_NextXmmword_1 lbl_EndOfLineFound: ;ECX contains the index of the first found character add rcx,rsi ;Compute the absolute address cmp rcx,gpObjDataEnd jge lbl_EndOfFile jmp lbl_ReadNextLine lbl_EndOfFile: LOG_TEXT szLogTotalObjects mov rcx,gnTotalObjects call WriteDecimalToLog LOG_TEXT szCRLF LOG_TEXT szLogTotalGroups mov rcx,gnTotalGroups call WriteDecimalToLog LOG_TEXT szCRLF LOG_TEXT szLogTotalSubGroups mov rcx,gnTotalSubGroups call WriteDecimalToLog LOG_TEXT szCRLF LOG_TEXT szLogTotalFaces mov rcx,gnTotalFaces call WriteDecimalToLog LOG_TEXT szCRLF LOG_TEXT szLogTotalVertices mov rcx,gnTotalVertices call WriteDecimalToLog LOG_TEXT szCRLF LOG_TEXT szLogTotalNormals mov rcx,gnTotalNormals call WriteDecimalToLog LOG_TEXT szCRLF LOG_TEXT szLogTotalTextureVectors mov rcx,gnTotalTextureVectors call WriteDecimalToLog LOG_TEXT szCRLF LOG_TEXT szLogTotalMtlLibs mov rcx,gnTotalMtlLibs call WriteDecimalToLog LOG_TEXT szCRLF ;Success mov rax,1 jmp lbl_End lbl_WinError: call SpellWinError xor rax,rax ;jmp lbl_End lbl_End: EPILOG countObjEntities endp То есть я большие участки перескакиваю, а там, где надо работать точечно, проверяю каждый байт
Если я правильно понял, у вас из-за каждого лишнего пробела/таба парсер будет падать, хотя это будет валидный .obj файл.
Наоборот: vpcmpistri игнорирует и пробелы, и вообще все значения, за исключением тех, что заданы во втором операнде. Тоже наоборот: не в проверку валидности (это ответственный участок), а в поиск символов EoL, т.е. LF и CR (где надо быстро проскочить). Скалярные инструкцииvpcmpistriLoading OBJ fileLoading OBJ fileOBJ file opened, size: 1653348OBJ file opened, size: 1653344RAM size calculated Memory Allocating: OK RAM allocated Loading MTL file MTL file opened, size: 1557 Memory Allocating: OK Materials: 7 MTL loaded successfullyObjects: 89 Groups: 89 SubGroups: 89 Faces: 17711Vertices: 12193Vertices: 12193Normals: 12008Normals: 12008Combined Vertices: 14909 Indices: 53133 Grouping faces by materials Material Groups: 7Texture vectors: 7525 Material Libraries Used: 1OBJ loaded successfullyOBJ loaded successfullyКод полностью отрабатывает токены "o", "g", "f", "v", "vn", "vt", "usemtl" и "mtllib"
Тогда ваш код гораздо умнее, чем я думал. Например: Находит первый символ из шаблона (v) Обрабатывает его Не возвращается к поиску в этой строке Сразу ищет конец строки
Да, метка lbl_ReadNextLine предполагает, что я нахожусь на новой строке. Там vpcmpistri ищет токен. Если находит - дорабатывает напильником, и тогда остаток символов в строке (в этой конкретной процедуре) его уже не интересует. Он прыгает на второй vpcmpistri, который ищет конец строки. И так до gpDataEnd --- Сообщение объединено, 16 авг 2026 в 04:19 --- Нет. Если решётка стоит в начале строки, то vpcmpistri найдёт её первой и покажет на неё
Мне почему стало интересно, иногда приходится парсить текстовые файлы. Я никогда на практике не использовал vpcmpistri, simd. Возник вопрос, можно ли применить для этой функции: Код (C): bool is_meaningful(char ch) { return isalnum(ch) || ch == '.' || ch == '-' || ch == '+' || ch == '/' || ch == '_'; } Например сделать таблицу-шаблон для символов: И проверять ее по 16 символов, или это бред ? Мне кажется это самое узкое место в моем парсинге. По сути ищем символ которого нет в шаблоне. Если simd по 16 символов проверяет, можно сделать парсинг в 16 раз быстрее.
Проверяет. Операнды vpcmpistri - это xmmword. Функцию is_meaningful(...) можно заменить одним интринсиком Upd: погорячился. A-Z одним интринсиком не заменишь, но вполне себе разумный цикл этого интринсика организовать можно. Другой вопрос: я не гарантирую эффективность, совместимость и т.д. Никто не знает, что там в микрокоде: с одной стороны - может оказаться, что cmpsb кэшируется эффективнее, с другой стороны - супер-пупер vpcmpistri может спровоцировать троттлинг. --- Сообщение объединено, 16 авг 2026 в 05:00 --- У меня тут чистый эксперимент, на грани стёба. Ё моё: "Москвич" на Вулкане, ещё и на ассемблере. По приколу я себе могу это позволить, а вот что-то продаваемое я бы так делать не рискнул
Ни cmpsb, ни xlat сами по себе не решают вопрос, т.к. обращаются к памяти. Если бы я постоянно работал со строками, то смотрел бы в сторону кэш-памяти, т.е. группы инструкций PREFETCH. Но у меня узкая специализация (3D-графика), поэтому для меня это ненадёжная практика, т.к. я не знаю, что происходит в микрокоде разных моделей и ревизий процессоров Intel и AMD. Первое, что бы я сделал - это взял бы среднестатистический текст, с которым Вы работаете, и ранжировал бы символы по вероятности их обнаружения. Тогда бы у меня была не таблица 'A'-'Z', а реальная жиза. Вот этой таблицей я бы прогрел кэш, закрепил бы его инструкцией PREFETCH, сделал 100500 разных сборок и протестировал бы на разных системах. Поскольку моя профессия - это 3D-графика, я балдею от SSE, AVX и FMA, а любое обращение к памяти для меня - это снижение плотности вычислений. Ваш E8400 - это Core2Duo, в нём нет SSE4.2, следовательно нет pcmpistri. Значит, я бы работал с тем, что есть. Ту самую ранжированную таблицу я бы загрузил во все РОН, в крайнем случае - в xmm и не пользовался бы RAM вообще
Вы - гений. Вы просто гений! Действительно, зачем мне каждый раз проверять WM_CREATE и WM_DESTROY, если они появляются всего один раз за всю жизнь экземпляра приложения?! Вот идеальный диспетчер сообщений: Код (ASM): .data gpWmCreate dq offset wmCreateProc gpWmDestroy dq offset wmDestroyProc gpWmSize dq offset wmSizeProc gpWmClose dq offset wmCloseProc gpWmCommand dq offset wmCommandProc LUT dq 1,2,5,10h,111h,0,0,0 .code WndProc proc movd xmm0,edx vpcmpistri xmm0,xmmword ptr[LUT],0Ch jс [pWmCreate + rcx*8] call DefWindowProcA WndProc endp --- Сообщение объединено, 17 авг 2026 в 09:39 --- Ещё можно ранжировать таблицу по вероятности: Код (ASM): .data gpWmCommand dq offset wmCommandProc ;Самое вероятное gpWmSize dq offset wmSizeProc ;Несколько раз gpWmClose dq offset wmCloseProc ;Один или несколько раз gpWmDestroy dq offset wmDestroyProc ;Один раз gpWmCreate dq offset wmCreateProc ;Один раз LUT dq 111h,5,10h,2,1,0,0,0 .code WndProc proc movd xmm0,edx vpcmpistri xmm0,xmmword ptr[LUT],0Ch jс [pWmCommand + rcx*8] call DefWindowProcA WndProc endp