blob: bdb525d11af107b03f3aa1ede4f0ea1eb3a935c0 [file] [log] [blame]
Willy Tarreau69c696c2015-04-28 10:18:09 +02001/*
2 * Copyright (C) 2015 Willy Tarreau <w@1wt.eu>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 * OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25#include <import/lru.h>
26
27/* Minimal list manipulation macros for lru64_list */
28#define LIST_ADD(lh, el) ({ (el)->n = (lh)->n; (el)->n->p = (lh)->n = (el); (el)->p = (lh); })
29#define LIST_DEL(el) ({ (el)->n->p = (el)->p; (el)->p->n = (el)->n; })
30
31/* Get key <key> from LRU cache <lru> for use with domain <domain> whose data's
32 * current revision is <revision>. If the key doesn't exist it's first created
33 * with ->domain = NULL. The caller detects this situation by checking ->domain
34 * and must perform the operation to be cached then call lru64_commit() to
35 * complete the operation. A lock (mutex or spinlock) may be added around the
36 * function to permit use in a multi-threaded environment. The function may
37 * return NULL upon memory allocation failure.
38 */
39struct lru64 *lru64_get(unsigned long long key, struct lru64_head *lru,
40 void *domain, unsigned long long revision)
41{
42 struct eb64_node *node;
43 struct lru64 *elem;
44
45 if (!lru->spare) {
46 if (!lru->cache_size)
47 return NULL;
48 lru->spare = malloc(sizeof(*lru->spare));
49 if (!lru->spare)
50 return NULL;
51 lru->spare->domain = NULL;
52 }
53
54 /* Lookup or insert */
55 lru->spare->node.key = key;
56 node = __eb64_insert(&lru->keys, &lru->spare->node);
57 elem = container_of(node, typeof(*elem), node);
58
59 if (elem != lru->spare) {
60 /* Existing entry found, check validity then move it at the
61 * head of the LRU list.
62 */
63 if (elem->domain == domain && elem->revision == revision) {
64 LIST_DEL(&elem->lru);
65 LIST_ADD(&lru->list, &elem->lru);
66 return elem;
67 }
68
69 if (!elem->domain)
70 return NULL; // currently locked
71
72 /* recycle this entry */
73 LIST_DEL(&elem->lru);
74 }
75 else {
76 /* New entry inserted, initialize and move to the head of the
77 * LRU list, and lock it until commit.
78 */
79 lru->cache_usage++;
80 lru->spare = NULL; // used, need a new one next time
81 }
82
83 elem->domain = NULL;
84 LIST_ADD(&lru->list, &elem->lru);
85
86 if (lru->cache_usage > lru->cache_size) {
87 /* try to kill oldest entry */
88 struct lru64 *old;
89
90 old = container_of(lru->list.p, typeof(*old), lru);
91 if (old->domain) {
92 /* not locked */
93 LIST_DEL(&old->lru);
94 __eb64_delete(&old->node);
95 if (!lru->spare)
96 lru->spare = old;
97 else
98 free(old);
99 lru->cache_usage--;
100 }
101 }
102 return elem;
103}
104
105/* Commit element <elem> with data <data>, domain <domain> and revision
106 * <revision>. <elem> is checked for NULL so that it's possible to call it
107 * with the result from a call to lru64_get(). The caller might lock it using a
108 * spinlock or mutex shared with the one around lru64_get().
109 */
110void lru64_commit(struct lru64 *elem, void *data, void *domain, unsigned long long revision)
111{
112 if (!elem)
113 return;
114
115 elem->data = data;
116 elem->revision = revision;
117 elem->domain = domain;
118}
119
120/* Create a new LRU cache of <size> entries. Returns the new cache or NULL in
121 * case of allocation failure.
122 */
123struct lru64_head *lru64_new(int size)
124{
125 struct lru64_head *lru;
126
127 lru = malloc(sizeof(*lru));
128 if (lru) {
129 lru->list.p = lru->list.n = &lru->list;
130 lru->keys = EB_ROOT_UNIQUE;
131 lru->spare = NULL;
132 lru->cache_size = size;
133 lru->cache_usage = 0;
134 }
135 return lru;
136}
137
138/* Tries to destroy the LRU cache <lru>. Returns the number of locked entries
139 * that prevent it from being destroyed, or zero meaning everything was done.
140 */
141int lru64_destroy(struct lru64_head *lru)
142{
143 struct lru64 *elem, *next;
144
145 if (!lru)
146 return 0;
147
148 elem = container_of(lru->list.p, typeof(*elem), lru);
149 while (&elem->lru != &lru->list) {
150 next = container_of(elem->lru.p, typeof(*next), lru);
151 if (elem->domain) {
152 /* not locked */
153 LIST_DEL(&elem->lru);
154 eb64_delete(&elem->node);
155 free(elem);
156 lru->cache_usage--;
157 lru->cache_size--;
158 }
159 elem = next;
160 }
161
162 if (lru->cache_usage)
163 return lru->cache_usage;
164
165 free(lru);
166 return 0;
167}
168
169/* The code below is just for validation and performance testing. It's an
170 * example of a function taking some time to return results that could be
171 * cached.
172 */
173#ifdef STANDALONE
174
175#include <stdio.h>
176
177static unsigned int misses;
178
179static unsigned long long sum(unsigned long long x)
180{
181#ifndef TEST_LRU_FAST_OPERATION
182 if (x < 1)
183 return 0;
184 return x + sum(x * 99 / 100 - 1);
185#else
186 return (x << 16) - (x << 8) - 1;
187#endif
188}
189
190static long get_value(struct lru64_head *lru, long a)
191{
192 struct lru64 *item;
193
194 if (lru) {
195 item = lru64_get(a, lru, lru, 0);
196 if (item && item->domain)
197 return (long)item->data;
198 }
199 misses++;
200 /* do the painful work here */
201 a = sum(a);
202 if (item)
203 lru64_commit(item, (void *)a, lru, 0);
204 return a;
205}
206
207/* pass #of loops in argv[1] and set argv[2] to something to use the LRU */
208int main(int argc, char **argv)
209{
210 struct lru64_head *lru = NULL;
211 long long ret;
212 int total, loops;
213
214 if (argc < 2) {
215 printf("Need a number of rounds and optionally an LRU cache size (0..65536)\n");
216 exit(1);
217 }
218
219 total = atoi(argv[1]);
220
221 if (argc > 2) /* cache size */
222 lru = lru64_new(atoi(argv[2]));
223
224 ret = 0;
225 for (loops = 0; loops < total; loops++) {
226 ret += get_value(lru, rand() & 65535);
227 }
228 /* just for accuracy control */
229 printf("ret=%llx, hits=%d, misses=%d (%d %% hits)\n", ret, total-misses, misses, (int)((float)(total-misses) * 100.0 / total));
230
231 while (lru64_destroy(lru));
232
233 return 0;
234}
235
236#endif