Здравствуйте всем.
Если вопрос глупый, не обижайтесь, я вроде искал - не нашел.
Нужна функция поиска подстроки в буфере, типа POS, но "подстрока" не символьная а произволный набор байт.
Естесс-но ведь, что обычный POS на первом же #0 байте в буфере остановиться, и дальше искать не будет.
Т.Е., что-то типа:
Searsh(ppp, bbb, L, k): integer;
где ppp - array[] of byte с "подстрокой" поиска,
bbb - array[] of byte с буфером где искать,
L - длина реальных данных в буфере,
k - длина "подстроки" в ppp.
Или подскажите, чем воспользоваться.
Я, конечно, могу написать сам, да вдруг велосипед уже существует.
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
12-10-2006 09:56 | Комментарий к предыдущим ответам
стала на немного опережатьСоврал невольно. Там была ошибка, исправил, в этом варианте стало в 1.5 - 2 раза на разных примерах медленней, чем MemoryStrPos (у меня тоже простой поиск).
12-10-2006 07:59 | Комментарий к предыдущим ответам
Этот вопрос упомянули сегодня в другом обсуждении. По случаю добавлю: DRON писал:
Вариант на Паскале работает в несколько раз медленнее.
Мы потом обменялись сообщениями на эту тему. Может быть, если считать такты, то действительно ASM будет эффективней. Но мои измерения времени не показали такого превосходства. Измерял скорость поиска в большой строке (мегабайты), в цикле несколько (последний раз 100) раз, т.к.измеряемые времена очень малы. Даже наоборот написанная на паскале функция после одной оптимизации вместо отставания на процентов 30%-50% (точно не помню) стала на немного опережать приведенную здесь функцию на ассемблере. Но существенный вывод, что поиск в строке, находящейся в памяти настолько быстр, что во многих ситуациях решающую роль, как и писал DRON, играют совсем другие моменты, например скорость чтения с диска.
Большое спасибо всем еще раз.
Я уж было и ветку потерял.
Пытаюсь использовать ваши примеры в программе.
Если не в этой, то в следующей - теперь уж точно не пропадет.
10-12-2005 15:47 | Комментарий к предыдущим ответам
Правда для БМХ использовал свой вариант, написанный на паскале, на который давал ссылку.
Собственно из него я свой "asm" и делал, неужели не узнали :)
Я измерения только в памяти производил (при работе с диском мы с большой вероятностью будем мерить производительность кэша, а не то, что нам надо), на объёмах в несколько мегабайт и мерил не время, а число тактов процессора. Вариант на Паскале работает в несколько раз медленнее. В принципе из асма можно ещё что-то выжать оптимизирую под конкретные конвейеры (попробуйте например переставить "mov al,[esi+edx]" и "dec ebx", скорость упадёт процентов на 10). Вообще, для данной задачи оптимизация по скорости ни к чему, скорость чтения с винта всяко меньше, надо просто файлы читать асинхронно, так чтобы пока новая порция читается, в старой происходил бы поиск.
10-12-2005 13:53 | Комментарий к предыдущим ответам
Что ж, если опыт показал, что при определенных, достаточно широких, условиях БМХ выигрыша не дает, то извиняюсь перед RSSDEW.
Я тоже попытался сравнить скорости. Правда для БМХ использовал свой вариант, написанный на паскале, на который давал ссылку. Измерял через GetTickCount на файлах до 22 МБ. Проверял для небольших подстрок размером примерно со слово: '[test]', '[rest]', потом удлинил до '[restaurant]' :). Для последней, более длинной строки БМХ несколько быстрее, а для первых разницы практически нет.
Похоже, что для многих практических применений оба способа достаточно хороши. На файле 22 мб время было ~70 ms. (Athlon 1800, 256 MB)
На меньших файлах у меня вообще сложно время измерить: есть сомнения в том, что за время измеряется, почему-то оно скачет "квантами" (по ~15 ms), если нажиматть кнопку теста несколько раз подряд, похоже на sliсe процесса, пришлось пускать поиск по 10 раз в цикле, чтобы что-то померить. Разбираться с GetProcessTimes не стал.
Надо признаться, по крайней мере в одном случае простой перебор был даже чуть быстрее, но это - практически в пределах погрешности. С учетом "квантов" поиск '[rest]' давал в обоих случаях чаще 235 мс, но для перебора иногда было ~218, а для БМХ иногда 250. Правда статистики я особо не набирал.
10-12-2005 05:59 | Комментарий к предыдущим ответам
DRON:
Ты бы оформил свой ответ в отдельный юнит и поместил бы в сокровищницу. Иначе такая полезная весчь на 80 процентов останется невостребованной(не найденной). Там в юните выведи отдельные функции для поиска как по stream'у так и в буфере (pointer/string).
type
TBMTable=record
Skips:array[Char] of Integer;
SubStr:string;
SubLen:Integer;
end;
function InitBMTable(const SubStr:string):TBMTable;
var
A,SubLen:Integer;
begin
SubLen:=Length(SubStr);
Result.SubStr:=SubStr;
Result.SubLen:=SubLen;
for A:=0 to 255 do
Result.Skips[Char(A)]:=SubLen;
for A:=1 to SubLen-1 do
Result.Skips[SubStr[A]]:=SubLen-A;
end;
function MemoryStrBMPosAsm(const Table:TBMTable;const Buffer;Size:Integer):Integer; register;
asm
push edi
push esi
push ebx
push edx
mov edi,eax
xor eax,eax
mov ebx,edx
add ecx,edx
mov esi,TBMTable.SubStr[edi]
mov edx,TBMTable.SubLen[edi]
lea ebx,[ebx+edx-1]
@1:
cmp ebx,ecx
jae @5
push ebx
push edx
@2:
dec edx
js @4
mov al,[esi+edx]
dec ebx
cmp al,[ebx+1]
je @2
pop edx
pop ebx
mov al,[ebx]
add ebx,dword ptr TBMTable.Skips[edi+eax*4]
jmp @1
@4:
pop edx
pop eax
sub eax,[esp]
sub eax,edx
inc eax
jmp @_End
@5:
xor eax,eax
dec eax
@_End:
pop edx
pop ebx
pop esi
pop edi
end;
function StrBMScan(Param:Pointer;const Buffer;Size:Integer):Integer;
begin
Profiler.Start;
Result:=MemoryStrBMPosAsm(TBMTable(Param^),Buffer,Size);
if Result<0 then Result:=-TBMTable(Param^).SubLen+1;
Profiler.Stop;
end;
Проверка на разных данных показывает, что БМ поиск начинает давать лучшие результаты, только для подстрок длиной более 6-ти символов. Двукратное ускорение начинается где-то после 128-и символов.
Конечно, скорость зависит от условий задачи. Но в БМХ нет "сравнения каждого байта на все имеющиеся в подстроке значения". В начале поиска генерируется массив сдвигов (пускай skips[]) для каждого из 256 символов, и смещение для символа buf[i] (buf - массив байтов, в котором ищем) определяется как skips[buf[i]]. http://www.delphikingdom.ru/asp/answer.asp?IDAnswer=27588
А обращение к элементу массива - операция быстрая.
Вообще хорошо известно, и DRON про это тоже написал, что БМХ - хороший алгоритм.
Вот здесь http://algolist.manual.ru/search/esearch/index.php
среднее время поиска применяемого Вами алгоритма "грубой силы" оценивается как O(2*n), где n - длина строки, в которой ищут. Для БМХ там же - O(n+m), m - длина подстроки. То есть для примера, где текст - кило-мега-гигабайты, а длина подстроки - до десятков байт, БМХ дает средний выигрыш в 2 раза.
>> ... и в просматриваемой строке дошли до символа "п", например, то ясно, что можно сразу сдвигаться на длину строки "арбуз" (т.к. в ней "п" нет), а не двигаться по 1 байту.
Это я все знаю. НО! Операция сравнения одного байта в цикле гораздо эффективнее, чем сравнение каждого байта на все имеющиеся в подстроке значения и переход по переменному шагу.
Как показывает практика, скорость не всегда увеличивается, далеко не всегда.
Вот, может пригодится. Универсальная функция сканирования потока:
type
//Result>=0 - строка найдена, начало в Buffer[Result]
//Result<0 - строка не найдена, кусок размером в -Result требует повторной обработки
TScanProc=function(Param:Pointer;const Buffer;Size:Integer):Integer;
function ScanStream(Stream:TStream;ScanProc:TScanProc;Param:Pointer=nil;BufferSize:Integer=65536):Int64;
var
Buffer:TByteDynArray;
Res,First,Readed:Integer;
begin
Result:=0;
SetLength(Buffer,BufferSize);
First:=0;
repeat
Readed:=Stream.Read(Buffer[First],BufferSize-First)+First;
if Readed=First then Break;
Res:=ScanProc(Param,Buffer[0],Readed);
if Abs(Res)>=Readed then Break;
Inc(Result,Res-First);
if Res>=0 then Exit;
Move(Buffer[Readed+Res],Buffer[0],-Res);
Inc(Result,Readed-Res);
First:=-Res;
until False;
Result:=-1;
end;
//Сканирование с использованием MemoryStrPos
//Но для длинных строк БМ-поиск действительно намного быстрее
function StrScan(Param:Pointer;const Buffer;Size:Integer):Integer;
begin
Result:=MemoryStrPos(string(Param),Buffer,Size);
if Result<0 then Result:=-Length(string(Param))+1;
end;
RSSDEW, все-таки советую посмотреть алгоритм БМХ или другой. Если совсем грубо, то если ищем подстроку "арбуз" и в просматриваемой строке дошли до символа "п", например, то ясно, что можно сразу сдвигаться на длину строки "арбуз" (т.к. в ней "п" нет), а не двигаться по 1 байту. Алгоритм это использует.
Сейчас так и ищу.
Сначала читал из файла в буфер по 8 КБ, затем переделал на отображение в память. Только все равно эффективнее оказалось обрабатывать куски из него "побуферно" (если так можно выразиться).
Задача именно такая: Имеются входные файлы, размером от десятков КБ до нескольких Гб. В них в потоковом режиме нужно находить другую последовательность (даже несколько), которые храняться в файлах настройки. При нахождении - с данной позиции первого файла делаем некоторую обработку, потом идем дальше.
Первый вариант функции поиска написал очень быстро: ищем в цикле по индексу от 0 до (N-L) первый байт из "подстроки" (длиной L), как нашли - сравниваем следующие L-1 байт из буфера с остатком "подстроки".
Скорость работы не замерял, но интуитивно ясно, что неоптимальная - этоже все-таки for, а не SCASB. Как дойдут руки - перепишу на асме.
А переписывать функции из библиотеки мне никогда не нравилось - по моему, они должны работать так, как изначально заложено (если вдруг это не явный баг, естесс-но).
Невозможно объявить с некоторой позиции отображения фиктивную String-переменную, поскольку тогда у нее по отрицательному смещению [-4] не будет реального счетчика длины.
А разве сложно взять функцию _LStrPos из System.pas и поменять в ней ДВЕ строчки, чтобы она работала не со строками, а с памятью:
function MemoryStrPos(const SubStr:AnsiString;const Buffer;Size:Integer):Integer; register;
asm
{ ->EAX Pointer to substr }
{ EDX Pointer to string }
{ <-EAX Position of substr in s or 0 }
TEST EAX,EAX
JE @@noWork
TEST EDX,EDX
JE @@stringEmpty
PUSH EBX
PUSH ESI
PUSH EDI
MOV ESI,EAX { Point ESI to substr }
MOV EDI,EDX { Point EDI to s }
//!!! MOV ECX,[EDI-skew].StrRec.length { ECX = Length(s) }
PUSH EDI { remember s position to calculate index }
MOV EDX,[ESI-4] { EDX = Length(substr) }
DEC EDX { EDX = Length(substr) - 1 }
JS @@fail { < 0 ? return 0 }
MOV AL,[ESI] { AL = first char of substr }
INC ESI { Point ESI to 2'nd char of substr }
SUB ECX,EDX { #positions in s to look at }
{ = Length(s) - Length(substr) + 1 }
JLE @@fail
@@loop:
REPNE SCASB
JNE @@fail
MOV EBX,ECX { save outer loop counter }
PUSH ESI { save outer loop substr pointer }
PUSH EDI { save outer loop s pointer }
MOV ECX,EDX
REPE CMPSB
POP EDI { restore outer loop s pointer }
POP ESI { restore outer loop substr pointer }
JE @@found
MOV ECX,EBX { restore outer loop counter }
JMP @@loop
@@fail:
POP EDX { get rid of saved s pointer }
XOR EAX,EAX
JMP @@exit
@@stringEmpty:
XOR EAX,EAX
JMP @@noWork
@@found:
POP EDX { restore pointer to first char of s }
MOV EAX,EDI { EDI points of char after match }
SUB EAX,EDX { the difference is the correct index }
@@exit:
POP EDI
POP ESI
POP EBX
@@noWork:
DEC EAX //!!!
end;
Если строка не найдена возвращает -1, если найдена, то от 0 до Size-1.
29-11-2005 09:57 | Комментарий к предыдущим ответам
RSSDEW: а вообще-то не сложно, но я к концу дня, только с шестого прочтения смог загрузить все пункты твоего message в свою мысль :o).
и написанное мое сообщение чуть раньше врятли подойдет в смысле скорости. Надо найти другой вариант.
RSSDEW (твои 2 последних ответа):
Как ты сложно мысль формулируешь... Как я понял ты хочешь, двигаясь по файлу отображенному в память, сравнивать блоки данных (1-ый из файла, 2-ой некая переменная с информацией)? так чего сложного? бежишь сравниваешь, для скорости, только первые байты, если равны, то сравниваешь последующие (определенной длины) или CompareMem.
Есть много алгоритмов поиска подстрок в строке, которые рассматривают и то, и другое просто как массив байтов. Можно почитать здесь: http://delphi.mtu-net.ru/zip/stephen.zip
Например, Бойер-Мур-Хорспул. Здесь на КС приводили парочку реализаций его. Одна из них, моего приготовления, использует строки, но переделать ее под массив байтов, я думаю, совсем несложно. Алгоритм несложный, но шустрый, особенно при больших подстроках. Может быть это пригодится.
И еще.
Приведенные примеры не особенно убеждают.
Точнее - не убеждают ни в чем.
Я прерасно знаю, что для строковых констант (а именно такие варианты вы приводите) Delphi генерит код, заранее совместимый с типами String, ShortString(если возможно), PChar и array[0..]of char.
Так в данном конкретном случае что функции вполне могут работать нормально.
P.S.
Вспоминая(из другой оперы) разговоры про оптимизацию, кто-то расказывал, как Watcom довольно сложный пример скомпилил в один вызов функции Format с константными параметрами.
Будь Delphi поумнее - он в данном случае тоже сгенерил бы заместо всех вычислений единственный оператор присваивания результата.
Всем спасибо за комментарии.
В SysUtils я первым делом и искал.
И потом сам написал функцию поиска, так что это уже пройдено. Но покольку эффективность не радует, решил спросить тут.
Вопросы все равно остаются:
1) Если просматривать больший массив поблочно, то при попадании "подстроки" на границе блоков она найдена не будет. Приходится сдвиг по массиву делать не кратным блоку, а меньше на лаг, заведомо больший длины "подстроки".
2) Информация находится в файле, поэтому я читаю не через поток, а выбрал способ с отображением файла в память. Но и тут не все гладко.
Невозможно объявить с некоторой позиции отображения фиктивную String-переменную, поскольку тогда у нее по отрицательному смещению [-4] не будет реального счетчика длины.
3) Если заводить отдельную переменную String и накладывать на нее буфер байт, то Delphi не поддерживает конструкцию absolute, поскольку длинные строки - суть динамически выделяемые массивы.
4) При помещении в буфер "подстроки" произвольных данных не получается применять Length, пока самостоятельно не задать SetLength. Но я же хочу иметь SetLength фиксированным (допустим = 100), а помещать в него подстроки любой длины - от 1 байта до 100 байт. А раздельных функций SetLength и SetAllocSize для длинных строк типа String не существует.
Так что подстрока в любом случае будет лежать в статическом array of byte, а длину реальных данных в нем приходится указывать через параметр.
А если передавть его в функции строкового поиска чарез приведение к PChar, то они-то КАК РАЗ реагируют на байт #0.
28-11-2005 06:32 | Комментарий к предыдущим ответам
Доброе утро,
Алексей: это маленький кусок бинарного файла с моего компа. Честно говоря
Я его просто скопировал открыв по F3 в командере. И ф-ция “POS” так-же как и POSEX
С такими строками не работает. Но ведь многие программы позволяют открывать
такие файлы как текстовые.
Max11:
У тебя в строке ошибка. Должно быть вот так: St1:= ' ¦-&N oh-L D@O OA@O AI#0#0zLEn?F; `YN §a(z)Cyn!O Y^ ¤+ 6b?`¦?"E NA Z%AL?O7ET--. gL?y'#0#0'E#! 1 + -*OAyZa AOTn C¦';
Самый тупой но дыропрочный вариант:
1)Создаешь Memorystream где будет находиться вся твоя информация
2)Cоздаешь динамический массив в который записываешь порцию своей
информации из потока
3)Создаешь второй динамический массив . В него кладешь искомую информацию
4)Создаешь цикл и просматриваешь в цикле необходимую последовательность
5)Естественно поток байтов и массивы байтов
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.