Skip to content

Commit 1564354

Browse files
authored
Replace ndpi_strnstr() implementation with an optimal one (#2447)
1 parent 5a25f89 commit 1564354

File tree

3 files changed

+114
-64
lines changed

3 files changed

+114
-64
lines changed

src/include/ndpi_api.h

+8-8
Original file line numberDiff line numberDiff line change
@@ -115,17 +115,17 @@ extern "C" {
115115
u_int32_t ndpi_get_tot_allocated_memory(void);
116116

117117
/**
118-
* Search the first occurrence of substring -find- in -s-
119-
* The search is limited to the first -slen- characters of the string
118+
* Finds the first occurrence of the substring 'needle' in the string 'haystack'.
120119
*
121-
* @par s = string to parse
122-
* @par find = string to match with -s-
123-
* @par slen = max length to match between -s- and -find-
124-
* @return a pointer to the beginning of the located substring;
125-
* NULL if the substring is not found
120+
* This function is similar to the standard `strstr()` function, but it has an additional parameter `len` that
121+
* specifies the maximum length of the search.
126122
*
123+
* @param haystack The string to search in.
124+
* @param needle The substring to search for.
125+
* @param len The maximum length of the search.
126+
* @return Pointer to the first occurrence of 'needle' in 'haystack', or NULL if no match is found.
127127
*/
128-
char* ndpi_strnstr(const char *s, const char *find, size_t slen);
128+
char *ndpi_strnstr(const char *haystack, const char *needle, size_t len);
129129

130130
/**
131131
* Same as ndpi_strnstr but case insensitive

src/lib/ndpi_main.c

+43-23
Original file line numberDiff line numberDiff line change
@@ -9610,32 +9610,52 @@ void ndpi_dump_risks_score(FILE *risk_out) {
96109610

96119611
/* ****************************************************** */
96129612

9613-
/*
9614-
* Find the first occurrence of find in s, where the search is limited to the
9615-
* first slen characters of s.
9616-
*/
9617-
char *ndpi_strnstr(const char *s, const char *find, size_t slen) {
9618-
char c;
9619-
size_t len;
9613+
char *ndpi_strnstr(const char *haystack, const char *needle, size_t len)
9614+
{
9615+
if (!haystack || !needle || len == 0)
9616+
{
9617+
return NULL;
9618+
}
9619+
9620+
size_t needle_len = strlen(needle);
9621+
size_t hs_real_len = strnlen(haystack, len);
96209622

9621-
if(s == NULL || find == NULL || slen == 0)
9623+
if (needle_len == 0)
9624+
{
9625+
return (char *)haystack;
9626+
}
9627+
9628+
if (needle_len > hs_real_len)
9629+
{
96229630
return NULL;
9631+
}
9632+
9633+
if (needle_len == 1)
9634+
{
9635+
return (char *)memchr(haystack, *needle, hs_real_len);
9636+
}
9637+
9638+
const char *current = haystack;
9639+
const char *haystack_end = haystack + hs_real_len;
9640+
9641+
while (current <= haystack_end - needle_len)
9642+
{
9643+
current = (const char *)memchr(current, *needle, haystack_end - current);
9644+
9645+
if (!current)
9646+
{
9647+
return NULL;
9648+
}
96239649

9624-
if((c = *find++) != '\0') {
9625-
len = strnlen(find, slen);
9626-
do {
9627-
char sc;
9628-
9629-
do {
9630-
if(slen-- < 1 || (sc = *s++) == '\0')
9631-
return(NULL);
9632-
} while(sc != c);
9633-
if(len > slen)
9634-
return(NULL);
9635-
} while(strncmp(s, find, len) != 0);
9636-
s--;
9637-
}
9638-
return((char *) s);
9650+
if ((current + needle_len <= haystack_end) && memcmp(current, needle, needle_len) == 0)
9651+
{
9652+
return (char *)current;
9653+
}
9654+
9655+
current++;
9656+
}
9657+
9658+
return NULL;
96399659
}
96409660

96419661
/* ****************************************************** */

tests/performance/strnstr.cpp

+63-33
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
#include <algorithm>
22
#include <chrono>
3-
#include <cmath>
43
#include <cstring>
54
#include <functional>
6-
#include <iomanip>
75
#include <iostream>
6+
#include <limits>
87
#include <map>
98
#include <random>
109
#include <string>
10+
#include <tuple>
1111
#include <vector>
1212

1313
char *ndpi_strnstr(const char *s, const char *find, size_t slen) {
@@ -30,40 +30,42 @@ char *ndpi_strnstr(const char *s, const char *find, size_t slen) {
3030
return ((char *)s);
3131
}
3232

33-
char *ndpi_strnstr_opt(const char *s, const char *find, size_t slen) {
34-
if (s == NULL || find == NULL || slen == 0) {
33+
char *ndpi_strnstr_opt(const char *haystack, const char *needle, size_t len) {
34+
if (!haystack || !needle || len == 0) {
3535
return NULL;
3636
}
3737

38-
char c = *find;
38+
size_t needle_len = strlen(needle);
39+
size_t hs_real_len = strnlen(haystack, len);
3940

40-
if (c == '\0') {
41-
return (char *)s;
41+
if (needle_len == 0) {
42+
return (char *)haystack;
4243
}
4344

44-
if (*(find + 1) == '\0') {
45-
return (char *)memchr(s, c, slen);
46-
}
47-
48-
size_t find_len = strnlen(find, slen);
49-
50-
if (find_len > slen) {
45+
if (needle_len > hs_real_len) {
5146
return NULL;
5247
}
5348

54-
const char *end = s + slen - find_len;
49+
if (needle_len == 1) {
50+
return (char *)memchr(haystack, *needle, hs_real_len);
51+
}
5552

56-
while (s <= end) {
57-
if (memcmp(s, find, find_len) == 0) {
58-
return (char *)s;
59-
}
53+
const char *current = haystack;
54+
const char *haystack_end = haystack + hs_real_len;
6055

61-
size_t remaining_length = end - s;
62-
s = (char *)memchr(s + 1, c, remaining_length);
56+
while (current <= haystack_end - needle_len) {
57+
current = (const char *)memchr(current, *needle, haystack_end - current);
6358

64-
if (s == NULL || s > end) {
59+
if (!current) {
6560
return NULL;
6661
}
62+
63+
if ((current + needle_len <= haystack_end) &&
64+
memcmp(current, needle, needle_len) == 0) {
65+
return (char *)current;
66+
}
67+
68+
current++;
6769
}
6870

6971
return NULL;
@@ -80,10 +82,9 @@ std::string random_string(size_t length, std::mt19937 &gen) {
8082

8183
double measure_time(const std::function<char *(const char *, const char *,
8284
size_t)> &strnstr_impl,
83-
const std::string &haystack, const std::string &needle,
84-
std::mt19937 &gen) {
85+
const std::string &haystack, const std::string &needle) {
8586
auto start = std::chrono::high_resolution_clock::now();
86-
// Call the function to prevent optimization
87+
8788
volatile auto result =
8889
strnstr_impl(haystack.c_str(), needle.c_str(), haystack.size());
8990
auto end = std::chrono::high_resolution_clock::now();
@@ -92,6 +93,31 @@ double measure_time(const std::function<char *(const char *, const char *,
9293
.count();
9394
}
9495

96+
void warm_up(const std::function<char *(const char *, const char *, size_t)>
97+
&strnstr_impl,
98+
const std::string &haystack, const std::string &needle,
99+
int iterations) {
100+
for (int i = 0; i < iterations; i++) {
101+
volatile auto result =
102+
strnstr_impl(haystack.c_str(), needle.c_str(), haystack.size());
103+
}
104+
}
105+
106+
double average_without_extremes(const std::vector<double> &times) {
107+
if (times.size() < 5) {
108+
return std::accumulate(times.begin(), times.end(), 0.0) /
109+
static_cast<double>(times.size());
110+
}
111+
112+
auto sorted_times = times;
113+
std::sort(sorted_times.begin(), sorted_times.end());
114+
sorted_times.erase(sorted_times.begin());
115+
sorted_times.pop_back();
116+
117+
return std::accumulate(sorted_times.begin(), sorted_times.end(), 0.0) /
118+
sorted_times.size();
119+
}
120+
95121
int main() {
96122
std::ios_base::sync_with_stdio(false);
97123
std::mt19937 gen(std::random_device{}());
@@ -105,10 +131,13 @@ int main() {
105131
const std::vector<std::pair<
106132
std::string, std::function<char *(const char *, const char *, size_t)>>>
107133
strnstr_impls = {
108-
{"ndpi_strnstr", ndpi_strnstr}, {"ndpi_strnstr_opt", ndpi_strnstr_opt}
109-
// Add other implementations for comparison here
134+
{"ndpi_strnstr", ndpi_strnstr},
135+
{"ndpi_strnstr_opt", ndpi_strnstr_opt},
110136
};
111137

138+
const int iterations = 100000;
139+
const int warm_up_iterations = 1000;
140+
112141
for (size_t haystack_len : haystack_lengths) {
113142
for (size_t needle_len : needle_lengths) {
114143
std::cout << "\nTest case - Haystack length: " << haystack_len
@@ -120,19 +149,20 @@ int main() {
120149
std::map<std::string, double> times;
121150

122151
for (const auto &impl : strnstr_impls) {
123-
double time_sum = 0.0;
124-
for (int i = 0; i < 100000; i++) {
125-
time_sum += measure_time(impl.second, haystack, needle, gen);
152+
warm_up(impl.second, haystack, needle, warm_up_iterations);
153+
154+
std::vector<double> times_vector;
155+
for (int i = 0; i < iterations; i++) {
156+
times_vector.push_back(measure_time(impl.second, haystack, needle));
126157
}
127-
double average_time =
128-
time_sum / 100000.0; // Average time in nanoseconds
158+
159+
double average_time = average_without_extremes(times_vector);
129160

130161
times[impl.first] = average_time;
131162
std::cout << "Average time for " << impl.first << ": " << average_time
132163
<< " ns\n";
133164
}
134165

135-
// Compare execution times between implementations
136166
std::string fastest_impl;
137167
double fastest_time = std::numeric_limits<double>::max();
138168
for (const auto &impl_time : times) {

0 commit comments

Comments
 (0)