Ну вот собственно, по совету crestа'ы переписал поиск на ассемблер. Вернее не весь, а критическую часть. Вот посмотрите может найдёте где можно оптимизировать.
Function Include_ScanEqu(ByVal pInclude As Long, ByVal IncLen As Long, ByVal pInclude2 As Long, ByVal IncLen2 As Long, ByVal pWordToSearch As Long, ByVal pWordToSearch_Len As Long)As Long
#Register None
'STDOUT FuncName$
Dim CmnSrch As Long, PartFlag As Long, SnglPartSrch As Long, EquIncPosPtr As Long
EquIncPosPtr=VarPtr(EquIncPos(0))
If IncLen=IncLen2 Then
CmnSrch=IncLen
ElseIf IncLen>IncLen2 Then
CmnSrch=IncLen2: SnglPartSrch=IncLen-IncLen2: PartFlag=1
ElseIf IncLen<IncLen2 Then
CmnSrch=IncLen: SnglPartSrch=IncLen2-IncLen: PartFlag=2
End If
If CmnSrch<0 Then Exit Function
Dim Tmp1 As Long, TmpAsc2 As Asciiz*3, TmpAscPtr As Asciiz Ptr
TmpAscPtr=VarPtr(TmpAsc2)
' HiWord(EDX)=Lcase$(Left$(WordToSearch,2))
! MOV EAX,TmpAscPtr
! MOV EDX,pWordToSearch
! MOV DX,Word PTR [EDX]
! MOV WORD PTR [EAX],DX
! PUSH EDX
TmpAsc2=LCase$(TmpAsc2)
! POP EDX
! SHL EDX,16
' End HiWord(EDX)
' EDX = Left$(WordToSearch,2)
' ECX = Len(1/2 part of include)
' ESI = 1/2 part of include (Left)
' EDI = 1/2 part of include (Right)
' EBX = Ubound(EquIncPos)
' EAX = VarPtr(EquIncPos(0))
Finish:
! MOV EquIncPos_Counter,EBX
'STDOUT "EquIncPos_Counter=" & str$(EquIncPos_Counter)
If EquIncPos_Counter>0 Then
EquIncPos_Counter=EquIncPos_Counter-1
Include_ScanEqu_Finish pInclude, pWordToSearch, pWordToSearch_Len
End If
End Function
Вот за что я люблю ассемблер, так за то что плевал я на все правила и стандарты
Это точно. Как говорят, "Что хочу, то и ворочу". И никто мне не указ, никаких ограничений.
Если я правильно понял, то это реализация INSTRING ? Комментариев ноль, поэтому приходится догадываться Два вызова api VarPtr сведут на нет всё, что можно наоптимизировать, т.к. эти два вызова потянут на очень много десятков тактов. А может и сотен. Имхо, остающиеся пару десятков написанных на асме оптимизировать не стоит. Скажем 200 тактов или 190 - невелика разница. Вот если бы избавиться от VarPtr, тем более, что это для временных переменных - было бы хорошо.
Я делал как-то поиск в строке подстроки (регистронезависимый), аналогия InString'а. Хорошо сохранился исходник, может подойдёт для твоего случая.
';===============================================
Instring proc Start:DWORD, StrAddr:DWORD, MatchAddr:DWORD, MatchLen:DWORD
push esi
push edi
push ebx
';------------------------------------------
mov ecx,Start ';стартовая позиция (поиск с начала строки: Start=1)
mov esi,StrAddr ';начало строки
@StartPos: ';поиск нуля в куске строки от начало+старт.позиция
cmp byte ptr [esi+ecx-1],0 ';до начала строки
je @OutOfString ';если встречен ноль - старт. позиция задана за пределами строки
loopnz @StartPos
';-------------------------------------------
cmp Start,0 ';"защита от дурака"
jle @InvalidStart
mov edx,MatchLen ';кол-во символов, которое должно совпасть
xor ecx,ecx
dec Start
add esi,Start ';абсолютный стартовый адрес
dec esi
mov edi,MatchAddr ';стартовый адрес MatchString
xor eax,eax ;для выхода с eax==0
align 16
@Loop1:
inc esi
@Loop2:
mov al,byte ptr[esi+ecx]
mov al,byte ptr[offset Table+eax]
mov bl,byte ptr[edi+ecx]
mov bl,byte ptr[offset Table+ebx]
test al,al ';если строка просмотрена до конца
jz @Ret ';выход с нулём в eax
;----------------------------------------
cmp al,bl ';поиск совпадения
je @Next
xor ecx,ecx ';если символы не совпадают, длина искомой цепочки=MatchLen
mov edi,MatchAddr ';Match-строку будем смотреть с начала
jmp @Loop1
@Next: ';если символы совпали
inc ecx
cmp ecx,edx
jne @Loop2
add esi,ecx
sub esi,edx ';если "очередь совпадения" закончилась,
';вычисление позиции с которой начинается
mov eax,StrAddr ';последовательность символов, совпадающая
sub esi,eax ';с MatchString
inc esi
mov eax,esi ';выход. В eax- позиция вхождения
jmp @Ret
@InvalidStart:
mov eax,-1
jmp @Ret
@OutOfString:
mov eax,-2
@Ret:
pop ebx
pop edi
pop esi
ret
Instring endp
Это проверенный вариант, возвращает номер позиции от начала строки, т.е. "Моя СтрОкА" и "сТРоКа" вернет 5.
Вызывать можно так:
 IM MyInclude AS STRING, MyMatch AS STRING
 IM FindOffset AS DWORD
MyInclude = "Длинная строка из файла, содержащего %WM_USER"
MyMatch = "%WM_"
FindOffset = Instring(1, BYVAL MyInclude, BYVAL MyMatch, 4)
ПБ-шную часть не проверял, в общем ф-ция ждет адреса строк.
Возвращенное значение можно где-нибудь запоминать, чтобы поиск в следующий раз выполнять не с начала инклюда, а с последнего найденного места.
Да, забыл про Table. Это байтовый массив (256 байт) для регистронезависимости. Можно делать "на лету" (как ты говорил в теме про сортировки TemplateString). И offset Table заменить на адрес этой строки (или массива байт).
Я использую свою таблицу:
.data
;------- таблица для case insensitive ----------
Table db 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
db 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63
db 64,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,91,92,93,94,95
db 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127
db 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159
db 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191
db 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
db 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
Вообще то да, догадаться тяжело. Да и твой код из МАСМА перевести в чистый ассемблер тоже не просто .
По поводу VarPtr - ну про количество тактов - это ты загнул , но то что это функция ты прав. Правда функция эта состоит из одной инструкции (не считая конечно CALL и RET), но это всё не важно, т.к. если ты заметил, VarPtr в цикле не участвует.
Вот описание того что оно делает:
Перед вызовом этой процедуры инклюд разбивается на две части. Указатели и длина каждой части передаются передаются сюда.
Параметры:
pInclude - указатель на первую половину инклюда
pInclude2 - указатель на вторую половину инклюда
IncLen - длина первой половины
IncLen2 - длина второй половины
pWordToSearch - указатель на искомое слово
pWordToSearch_Len - длина искомого слова.
Теперь код:
1. В EDX заносится в старшую половину LCase$(Left$(WordToSearch,2)), а в младшую соответственно Ucase. Т.е. первые 2 байта в верхнем и нижнем регистре.
2. В ECX - есессно длина (и ежу понятно - а куда ж ещё)
3. В ESI - указатель на 1-ю половину
4. В EDI - указатель на 2-ю половину
5. В EAX - указатель на предварительно размерянный массив Long
6. В EBX - индекс массива (ну того, который в EAX)
Ну а дальше всё просто, в проверяем последовательно слова(WORD) первой и второй половины и если совпадают - заносятся в массив (EAX+EBX*4)
'If Ucase$(Left$(WordToSearch,2))=@ESI(ECX) Then
! CMP DX, WORD PTR [ESI]
! JE WrdFound
'ElseIf Ucase$(Left$(WordToSearch,2))=@EDI(ECX) Then
! CMP DX, WORD PTR [EDI]
! JE WrdFound2
' Здесь вертим EDX на 16 бит чтобы в DX стало LCase.
! ROR EDX,16
' Соответсвенно проверяем на LCase
! CMP DX, WORD PTR [ESI]
! JE WrdFound_1
! CMP DX, WORD PTR [EDI]
! JE WrdFound2_1
'Вертим обратно чтобы опять стало Ucase
! ROR EDX,16
' Ну это я думаю коментировать не надо
ContLoop:
! DEC ECX
! JZ Finish
! INC ESI
! INC EDI
! JMP WhileLen
Ну а метки WrdFound - это занос позиции в заранее отведённый для этого массив.
Ну вот и всё. Дальше вызывается процедура (Include_ScanEqu_Finish), которая вычищает мусор и остаются только подходящие под условие строки.
Всё это выполняется за доли секунды (включая чистку мусора). Прирост впечатляющий. Правда я пока код заполнения ListBox'а не делал. Может он всю оптимизацию и убьёт
ZagZag, .data и .const отличаются только правами, const - это readonly, data - readwrite.
По скорости разницы не будет. В секцию const писать нельзя, только читать, иначе программа упадёт.
А код перевести не так уж и сложно, пересчитать и заменить параметры, например StrAddr на dword ptr ss:[esp/ebp+???]. И всё. Ну с учетом того, что пушились три регистра. (+12)
Может он всю оптимизацию и убьёт
Наверняка так и будет
Замени на virtual listview, с ним правда сложнее, чем с листбоксом, и надо будет держать в памяти все куски строк, удовлетворяющие твоему InString, но зато это намного быстрее.
Хм, не знаю право стоит ли. В ListBox'е всё просто - LB_ADDSTRING и LB_FINDSTRING. А в листвью ещё и уведомления нужно обрабатывать, а я их не люблю почему то (да ComCtl.INC лишний раз подключать не хочется) . Попробую для начала ListBox, а если не получится - тогда уж ListView.
Сейчас переделываю прогу. Есть массив (строка) 16 Мб. Подготовить эту строку, скомпоновать в ней все данные в виде массива структур (50000 по 320 байт) занимает 300-400 мс. А уже все готовое по SendMessage запихать в листбокс или листвью - больше 30 сек. Просто 50000 раз SendMessage...
Поэтому решил вернуться к старому доброму virtual listview. Ему вообще ничего не надо записывать. Чтобы дать ему по запросу эти несколько строк, которые видны в его окне, времени практически 0 надо
ListBox повержен. Всё до примитивности просто.
Надеюсь что и в NT системах будет также.
Проверил на самом большом списке констант %ERROR_...(около 1500 элементов), загружает моментально.
Проверял только на глобальном массив строк, так что если с другими типами не получится - не обессудьте.
На входе LB_ADDSTRING стребует ASCIIZ, почему бы не превратить временно нашу наш многомегобайтный участок памяти в коретенькую строку типа ASCIIZ, а после добавления не вернуть её обратно? Патчится один байт в память до и после SendMessage.
А вот и сама процедура. На входе: Параметер1 - указатель на начало строки, Параметер2 - длина.
Sub ShowListBoxEqu_AddItem(ByVal ItemPos As Long, ByVal ItemLen As Long)
Dim WH_S As SIZEL
GetTextExtentPoint32 GetDC(hListBox), ByVal ItemPos, ItemLen, WH_S
If WH_S.cX>Item_LargestWidth Then Item_LargestWidth=WH_S.cX
Dim ToAddPtr As Asciiz Ptr
ToAddPtr=ItemPos
Dim PatchToZero As Byte Ptr
PatchToZero=ItemPos
@PatchToZero[ItemLen]=0
SendMessage hListBox, %LB_ADDSTRING, 0, ByVal ItemPos
@PatchToZero[ItemLen]=13
End Sub
Ну да скажем 1500 это явно маловато... я для клиента брал 20 000 записей...
Когда-то он загружался моментально, но были отормоза с отображением в ListView (данные прям из БД брались). Сейчас он пол года загружается, но хоть отображает сравнительно шустро...
Т.ч. надо бы брать больше...
PS
Как оказалось в VB очень тормознутый вызов АПИ, с одним челом брали код по ручному переводу юникодовой строки в ASCII, дык он в цикле Mid'ом делал в два раза библиотеку написанную мной на асьме...
А позже мы попробовали взять без цикла но большой объем данных - в итоге библа на асьме в 10 с лишним раз оказалась шустрей...
Так что чем меньше АПИ мы вызываем, тем получается быстрей что-ль...
Забыл добавить что при копировании участка памяти в строку типа ASCIIZ перед добавлением, 1500 элементов загружались почти минуту. А здесь доли секунды. Прогресс ошеломляющий. О причинах только догадываюсь.
Как оказалось в VB очень тормознутый вызов АПИ
Вообще то это для PB код. Если хочешь - потести для VB. Заодно и API ListBox свой проапгрейдишь если получится.