Browse code

Merge pull request #132 from jhawthorn/no_vla

Avoid use of VLA and reduce memory usage in match to O(m)

John Hawthorn authored on 28/12/2019 06:26:48 • GitHub committed on 28/12/2019 06:26:48
Showing 5 changed files

... ...
@@ -1,7 +1,7 @@
1 1
 VERSION=1.0
2 2
 
3 3
 CPPFLAGS=-DVERSION=\"${VERSION}\" -D_GNU_SOURCE
4
-CFLAGS+=-Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps
4
+CFLAGS+=-Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps -Werror=vla
5 5
 PREFIX?=/usr/local
6 6
 MANDIR?=$(PREFIX)/share/man
7 7
 BINDIR?=$(PREFIX)/bin
... ...
@@ -4,6 +4,7 @@
4 4
 #include <stdio.h>
5 5
 #include <float.h>
6 6
 #include <math.h>
7
+#include <stdlib.h>
7 8
 
8 9
 #include "match.h"
9 10
 #include "bonus.h"
... ...
@@ -27,122 +28,177 @@ int has_match(const char *needle, const char *haystack) {
27 28
 	return 1;
28 29
 }
29 30
 
31
+#define SWAP(x, y, T) do { T SWAP = x; x = y; y = SWAP; } while (0)
32
+
30 33
 #define max(a, b) (((a) > (b)) ? (a) : (b))
31 34
 
32
-#ifdef DEBUG_VERBOSE
33
-/* print one of the internal matrices */
34
-void mat_print(score_t *mat, char name, const char *needle, const char *haystack) {
35
-	int n = strlen(needle);
36
-	int m = strlen(haystack);
37
-	int i, j;
38
-	fprintf(stderr, "%c   ", name);
39
-	for (j = 0; j < m; j++) {
40
-		fprintf(stderr, "     %c", haystack[j]);
41
-	}
42
-	fprintf(stderr, "\n");
43
-	for (i = 0; i < n; i++) {
44
-		fprintf(stderr, " %c |", needle[i]);
45
-		for (j = 0; j < m; j++) {
46
-			score_t val = mat[i * m + j];
47
-			if (val == SCORE_MIN) {
48
-				fprintf(stderr, "    -\u221E");
49
-			} else {
50
-				fprintf(stderr, " %.3f", val);
51
-			}
52
-		}
53
-		fprintf(stderr, "\n");
54
-	}
55
-	fprintf(stderr, "\n\n");
56
-}
57
-#endif
35
+struct match_struct {
36
+	int needle_len;
37
+	int haystack_len;
38
+
39
+	char lower_needle[MATCH_MAX_LEN];
40
+	char lower_haystack[MATCH_MAX_LEN];
41
+
42
+	score_t match_bonus[MATCH_MAX_LEN];
43
+};
58 44
 
59 45
 static void precompute_bonus(const char *haystack, score_t *match_bonus) {
60 46
 	/* Which positions are beginning of words */
61
-	int m = strlen(haystack);
62 47
 	char last_ch = '/';
63
-	for (int i = 0; i < m; i++) {
48
+	for (int i = 0; haystack[i]; i++) {
64 49
 		char ch = haystack[i];
65 50
 		match_bonus[i] = COMPUTE_BONUS(last_ch, ch);
66 51
 		last_ch = ch;
67 52
 	}
68 53
 }
69 54
 
70
-score_t match_positions(const char *needle, const char *haystack, size_t *positions) {
55
+static void setup_match_struct(struct match_struct *match, const char *needle, const char *haystack) {
56
+	match->needle_len = strlen(needle);
57
+	match->haystack_len = strlen(haystack);
58
+
59
+	if (match->needle_len > MATCH_MAX_LEN || match->needle_len > match->haystack_len) {
60
+		return;
61
+	}
62
+
63
+	for (int i = 0; i < match->needle_len; i++)
64
+		match->lower_needle[i] = tolower(needle[i]);
65
+
66
+	for (int i = 0; i < match->haystack_len; i++)
67
+		match->lower_haystack[i] = tolower(haystack[i]);
68
+
69
+	precompute_bonus(haystack, match->match_bonus);
70
+}
71
+
72
+static inline void match_row(const struct match_struct *match, int row, score_t *curr_D, score_t *curr_M, const score_t *last_D, const score_t *last_M) {
73
+	int n = match->needle_len;
74
+	int m = match->haystack_len;
75
+	int i = row;
76
+
77
+	const char *lower_needle = match->lower_needle;
78
+	const char *lower_haystack = match->lower_haystack;
79
+	const score_t *match_bonus = match->match_bonus;
80
+
81
+	score_t prev_score = SCORE_MIN;
82
+	score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER;
83
+
84
+	for (int j = 0; j < m; j++) {
85
+		if (lower_needle[i] == lower_haystack[j]) {
86
+			score_t score = SCORE_MIN;
87
+			if (!i) {
88
+				score = (j * SCORE_GAP_LEADING) + match_bonus[j];
89
+			} else if (j) { /* i > 0 && j > 0*/
90
+				score = max(
91
+						last_M[j - 1] + match_bonus[j],
92
+
93
+						/* consecutive match, doesn't stack with match_bonus */
94
+						last_D[j - 1] + SCORE_MATCH_CONSECUTIVE);
95
+			}
96
+			curr_D[j] = score;
97
+			curr_M[j] = prev_score = max(score, prev_score + gap_score);
98
+		} else {
99
+			curr_D[j] = SCORE_MIN;
100
+			curr_M[j] = prev_score = prev_score + gap_score;
101
+		}
102
+	}
103
+}
104
+
105
+score_t match(const char *needle, const char *haystack) {
71 106
 	if (!*needle)
72 107
 		return SCORE_MIN;
73 108
 
74
-	int n = strlen(needle);
75
-	int m = strlen(haystack);
109
+	struct match_struct match;
110
+	setup_match_struct(&match, needle, haystack);
76 111
 
77
-	if (n == m) {
112
+	int n = match.needle_len;
113
+	int m = match.haystack_len;
114
+
115
+	if (m > MATCH_MAX_LEN || n > m) {
116
+		/*
117
+		 * Unreasonably large candidate: return no score
118
+		 * If it is a valid match it will still be returned, it will
119
+		 * just be ranked below any reasonably sized candidates
120
+		 */
121
+		return SCORE_MIN;
122
+	} else if (n == m) {
78 123
 		/* Since this method can only be called with a haystack which
79 124
 		 * matches needle. If the lengths of the strings are equal the
80 125
 		 * strings themselves must also be equal (ignoring case).
81 126
 		 */
82
-		if (positions)
83
-			for (int i = 0; i < n; i++)
84
-				positions[i] = i;
85 127
 		return SCORE_MAX;
86 128
 	}
87 129
 
88
-	if (m > 1024) {
130
+	/*
131
+	 * D[][] Stores the best score for this position ending with a match.
132
+	 * M[][] Stores the best possible score at this position.
133
+	 */
134
+	score_t D[2][MATCH_MAX_LEN], M[2][MATCH_MAX_LEN];
135
+
136
+	score_t *last_D, *last_M;
137
+	score_t *curr_D, *curr_M;
138
+
139
+	last_D = D[0];
140
+	last_M = M[0];
141
+	curr_D = D[1];
142
+	curr_M = M[1];
143
+
144
+	for (int i = 0; i < n; i++) {
145
+		match_row(&match, i, curr_D, curr_M, last_D, last_M);
146
+
147
+		SWAP(curr_D, last_D, score_t *);
148
+		SWAP(curr_M, last_M, score_t *);
149
+	}
150
+
151
+	return last_M[m - 1];
152
+}
153
+
154
+score_t match_positions(const char *needle, const char *haystack, size_t *positions) {
155
+	if (!*needle)
156
+		return SCORE_MIN;
157
+
158
+	struct match_struct match;
159
+	setup_match_struct(&match, needle, haystack);
160
+
161
+	int n = match.needle_len;
162
+	int m = match.haystack_len;
163
+
164
+	if (m > MATCH_MAX_LEN || n > m) {
89 165
 		/*
90 166
 		 * Unreasonably large candidate: return no score
91 167
 		 * If it is a valid match it will still be returned, it will
92 168
 		 * just be ranked below any reasonably sized candidates
93 169
 		 */
94 170
 		return SCORE_MIN;
171
+	} else if (n == m) {
172
+		/* Since this method can only be called with a haystack which
173
+		 * matches needle. If the lengths of the strings are equal the
174
+		 * strings themselves must also be equal (ignoring case).
175
+		 */
176
+		if (positions)
177
+			for (int i = 0; i < n; i++)
178
+				positions[i] = i;
179
+		return SCORE_MAX;
95 180
 	}
96 181
 
97
-	char lower_needle[n];
98
-	char lower_haystack[m];
99
-
100
-	for (int i = 0; i < n; i++)
101
-		lower_needle[i] = tolower(needle[i]);
102
-
103
-	for (int i = 0; i < m; i++)
104
-		lower_haystack[i] = tolower(haystack[i]);
105
-
106
-	score_t match_bonus[m];
107
-	score_t D[n][m], M[n][m];
108
-
109 182
 	/*
110 183
 	 * D[][] Stores the best score for this position ending with a match.
111 184
 	 * M[][] Stores the best possible score at this position.
112 185
 	 */
113
-	precompute_bonus(haystack, match_bonus);
186
+	score_t (*D)[MATCH_MAX_LEN], (*M)[MATCH_MAX_LEN];
187
+	M = malloc(sizeof(score_t) * MATCH_MAX_LEN * n);
188
+	D = malloc(sizeof(score_t) * MATCH_MAX_LEN * n);
189
+
190
+	score_t *last_D, *last_M;
191
+	score_t *curr_D, *curr_M;
114 192
 
115 193
 	for (int i = 0; i < n; i++) {
116
-		score_t prev_score = SCORE_MIN;
117
-		score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER;
118
-
119
-		for (int j = 0; j < m; j++) {
120
-			if (lower_needle[i] == lower_haystack[j]) {
121
-				score_t score = SCORE_MIN;
122
-				if (!i) {
123
-					score = (j * SCORE_GAP_LEADING) + match_bonus[j];
124
-				} else if (j) { /* i > 0 && j > 0*/
125
-					score = max(
126
-					    M[i - 1][j - 1] + match_bonus[j],
127
-
128
-					    /* consecutive match, doesn't stack with match_bonus */
129
-					    D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE);
130
-				}
131
-				D[i][j] = score;
132
-				M[i][j] = prev_score = max(score, prev_score + gap_score);
133
-			} else {
134
-				D[i][j] = SCORE_MIN;
135
-				M[i][j] = prev_score = prev_score + gap_score;
136
-			}
137
-		}
138
-	}
194
+		curr_D = &D[i][0];
195
+		curr_M = &M[i][0];
139 196
 
140
-#ifdef DEBUG_VERBOSE
141
-	fprintf(stderr, "\"%s\" =~ \"%s\"\n", needle, haystack);
142
-	mat_print(&D[0][0], 'D', needle, haystack);
143
-	mat_print(&M[0][0], 'M', needle, haystack);
144
-	fprintf(stderr, "\n");
145
-#endif
197
+		match_row(&match, i, curr_D, curr_M, last_D, last_M);
198
+
199
+		last_D = curr_D;
200
+		last_M = curr_M;
201
+	}
146 202
 
147 203
 	/* backtrace to find the positions of optimal matching */
148 204
 	if (positions) {
... ...
@@ -173,9 +229,10 @@ score_t match_positions(const char *needle, const char *haystack, size_t *positi
173 229
 		}
174 230
 	}
175 231
 
176
-	return M[n - 1][m - 1];
177
-}
232
+	score_t result = M[n - 1][m - 1];
178 233
 
179
-score_t match(const char *needle, const char *haystack) {
180
-	return match_positions(needle, haystack, NULL);
234
+	free(M);
235
+	free(D);
236
+
237
+	return result;
181 238
 }
... ...
@@ -7,6 +7,8 @@ typedef double score_t;
7 7
 #define SCORE_MAX INFINITY
8 8
 #define SCORE_MIN -INFINITY
9 9
 
10
+#define MATCH_MAX_LEN 1024
11
+
10 12
 int has_match(const char *needle, const char *haystack);
11 13
 score_t match_positions(const char *needle, const char *haystack, size_t *positions);
12 14
 score_t match(const char *needle, const char *haystack);
... ...
@@ -36,8 +36,8 @@ static void draw_match(tty_interface_t *state, const char *choice, int selected)
36 36
 	char *search = state->last_search;
37 37
 
38 38
 	int n = strlen(search);
39
-	size_t positions[n + 1];
40
-	for (int i = 0; i < n + 1; i++)
39
+	size_t positions[MATCH_MAX_LEN];
40
+	for (int i = 0; i < n + 1 && i < MATCH_MAX_LEN; i++)
41 41
 		positions[i] = -1;
42 42
 
43 43
 	score_t score = match_positions(search, choice, &positions[0]);
... ...
@@ -135,8 +135,8 @@ TEST test_choices_unicode() {
135 135
 }
136 136
 
137 137
 TEST test_choices_large_input() {
138
-	int N = 100000;
139
-	char *strings[N];
138
+	const int N = 100000;
139
+	char *strings[100000];
140 140
 
141 141
 	for(int i = 0; i < N; i++) {
142 142
 		asprintf(&strings[i], "%i", i);