Merge lp:~epics-core/epics-base/bigdb into lp:~epics-core/epics-base/3.16

Proposed by mdavidsaver
Status: Rejected
Rejected by: Ralph Lange
Proposed branch: lp:~epics-core/epics-base/bigdb
Merge into: lp:~epics-core/epics-base/3.16
Diff against target: 358 lines (+202/-38)
8 files modified
src/ioc/db/dbScan.c (+2/-2)
src/ioc/dbStatic/dbBase.h (+19/-19)
src/ioc/dbStatic/dbLexRoutines.c (+19/-1)
src/ioc/dbStatic/dbStaticLib.c (+2/-13)
src/libCom/ellLib/Makefile (+1/-0)
src/libCom/ellLib/ellLib.h (+2/-0)
src/libCom/ellLib/ellSort.c (+83/-0)
src/libCom/test/epicsEllTest.c (+74/-3)
To merge this branch: bzr merge lp:~epics-core/epics-base/bigdb
Reviewer Review Type Date Requested Status
Ralph Lange Needs Resubmitting
Andrew Johnson Needs Fixing
Review via email: mp+292997@code.launchpad.net

Description of the change

A couple of weeks ago I made a semi-intentional test of what happens to an IOC with >100k records. It works quite well, if you have the patience to wait for it to start up. Some profiling found two hot spots, both list insertion sorts, in dbCreateRecord() and addToList(). trans() in macCore.c is a distant third.

The loop in addToList() is written in such a way that the common case (all PHAS==0) always traverses the entire list. I change this the common case simply appends, and the worst case is unchanged.

dbCreateRecord() is harder to fix while still maintaining the sorting*. I settled on re-sorting once per call to dbReadCOM(), which works except for dbReadTemplate() with lots of little templates.

* It would be even better to sort once in iocInit(), or not at all, but I wasn't sure if anything depends on the ordering.

To post a comment you must log in.
Revision history for this message
Andrew Johnson (anj) wrote :

Interesting.

Please adjust or remove the comment at dbStaticLib.c:1415. I can't immediately think of a reason why records still need to be sorted; that particular comment in dbCreateRecord() dates back to when dbStaticLib.c was first added to CVS in 1993 (commit #905). There was already a hash table which was used for name lookups in that version, so it wasn't to speed up name searches. It was most likely because back in the days of binary database files and the curses-based dct (database configuration tool), record names for editing were presented to the user in a list, and it would have been very unfriendly to not have sorted it first. Have you tried removing the sort completely?

The new ellSort.c file should have a copyright/license header. I don't recognize any of the chiark website's example C code in it, so I'm guessing you wrote this yourself from scratch just using the algorithm and we don't need to import their copyright notice or license text at all.

lp:~epics-core/epics-base/bigdb updated
12741. By mdavidsaver

fixup comments

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> Please adjust or remove the comment at dbStaticLib.c:1415.
> The new ellSort.c file should have a copyright/license header.

Done

> I don't recognize any of the chiark website's example C code
> in it, so I'm guessing you wrote this yourself from scratch

Correct. I just want to give due credit. I went looking for the best way to sort linked lists, and there was a well thought out answer waiting.

> Have you tried removing the sort completely?

Yup. Works fine. I couldn't find anything in Base which depends on the ordering.

> ... dates back to when dbStaticLib.c was first added to CVS in 1993 ...

I know. There was dust, and spiders.

Revision history for this message
Ralph Lange (ralph-lange) wrote :

Looks good (with two minor issues in comments).

review: Approve
lp:~epics-core/epics-base/bigdb updated
12742. By mdavidsaver

ellSort: comments and redundant asserts

Revision history for this message
mdavidsaver (mdavidsaver) :
Revision history for this message
Andrew Johnson (anj) wrote :

dbCreateAlias() also needs to be updated, it still does an insertion sort.

If we remove the sort completely dbRenameRecord() would also need changes, although it could be removed completely from 3.15 or 3.16 as it is only useful as part of the old dbStaticHost library by Database Configuration Tools which these branches don't build any more.

review: Needs Fixing
Revision history for this message
Ralph Lange (ralph-lange) wrote :

I will take care of the remaining things and prepare a merge proposal for the 3.15 branch, including removal of dbRenameRecord() - I had been wondering what it was good for a couple of times.

A possible backport to 3.14 would have to add the changes to dbRenameRecord().

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Thank you Ralph. I'm trying to spend what little dev. time I have atm. on tests for the link support code.

Revision history for this message
Ralph Lange (ralph-lange) wrote :

This merge request has been superseded by its 3.15 rebase,
https://code.launchpad.net/~epics-core/epics-base/bigdb-3.15

review: Needs Resubmitting

Unmerged revisions

12742. By mdavidsaver

ellSort: comments and redundant asserts

12741. By mdavidsaver

fixup comments

12740. By mdavidsaver

test ellSortStable

12739. By mdavidsaver

dbCreateRecord use ellSortStable()

sort records once per dbLoadCOM()

12738. By mdavidsaver

ellSort

12737. By mdavidsaver

dbScan: optimize addToList

Insert from back to maintain ~same order.
Avoid iterating entire list each time
in the common case where all PHAS==0

12736. By mdavidsaver

whitespace

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/ioc/db/dbScan.c'
2--- src/ioc/db/dbScan.c 2015-09-07 04:15:21 +0000
3+++ src/ioc/db/dbScan.c 2016-04-27 12:02:50 +0000
4@@ -989,9 +989,9 @@
5 pse->precord = precord;
6 }
7 pse->pscan_list = psl;
8- ptemp = (scan_element *)ellFirst(&psl->list);
9+ ptemp = (scan_element *)ellLast(&psl->list);
10 while (ptemp) {
11- if (ptemp->precord->phas > precord->phas) {
12+ if (ptemp->precord->phas <= precord->phas) {
13 ellInsert(&psl->list, ellPrevious(&ptemp->node), &pse->node);
14 break;
15 }
16
17=== modified file 'src/ioc/dbStatic/dbBase.h'
18--- src/ioc/dbStatic/dbBase.h 2015-02-03 06:38:23 +0000
19+++ src/ioc/dbStatic/dbBase.h 2016-04-27 12:02:50 +0000
20@@ -134,25 +134,25 @@
21 }dbVariableDef;
22
23 typedef struct dbRecordType {
24- ELLNODE node;
25- ELLLIST attributeList; /*LIST head of attributes*/
26- ELLLIST recList; /*LIST head of sorted dbRecordNodes*/
27- ELLLIST devList; /*List of associated device support*/
28- ELLLIST cdefList; /*LIST of Cdef text items*/
29- char *name;
30- short no_fields; /* number of fields defined */
31- short no_prompt; /* number of fields to configure*/
32- short no_links; /* number of links */
33- short no_aliases; /* number of aliases in recList */
34- short *link_ind; /* addr of array of ind in papFldDes*/
35- char **papsortFldName;/* ptr to array of ptr to fld names*/
36- short *sortFldInd; /* addr of array of ind in papFldDes*/
37- dbFldDes *pvalFldDes; /*pointer dbFldDes for VAL field*/
38- short indvalFlddes; /*ind in papFldDes*/
39- dbFldDes **papFldDes; /* ptr to array of ptr to fldDes*/
40- /*The following are only available on run time system*/
41- struct rset *prset;
42- int rec_size; /*record size in bytes */
43+ ELLNODE node;
44+ ELLLIST attributeList; /*LIST head of attributes*/
45+ ELLLIST recList; /*LIST head of sorted dbRecordNodes*/
46+ ELLLIST devList; /*List of associated device support*/
47+ ELLLIST cdefList; /*LIST of Cdef text items*/
48+ char *name;
49+ short no_fields; /* number of fields defined */
50+ short no_prompt; /* number of fields to configure*/
51+ short no_links; /* number of links */
52+ short no_aliases; /* number of aliases in recList */
53+ short *link_ind; /* addr of array of ind in papFldDes*/
54+ char **papsortFldName;/* ptr to array of ptr to fld names*/
55+ short *sortFldInd; /* addr of array of ind in papFldDes*/
56+ dbFldDes *pvalFldDes; /*pointer dbFldDes for VAL field*/
57+ short indvalFlddes; /*ind in papFldDes*/
58+ dbFldDes **papFldDes; /* ptr to array of ptr to fldDes*/
59+ /*The following are only available on run time system*/
60+ struct rset *prset;
61+ int rec_size; /*record size in bytes */
62 }dbRecordType;
63
64 struct dbPvd; /* Contents private to dbPvdLib code */
65
66=== modified file 'src/ioc/dbStatic/dbLexRoutines.c'
67--- src/ioc/dbStatic/dbLexRoutines.c 2016-02-27 00:16:26 +0000
68+++ src/ioc/dbStatic/dbLexRoutines.c 2016-04-27 12:02:50 +0000
69@@ -195,7 +195,16 @@
70 free((void *)pinputFileNow);
71 }
72 }
73-
74
75+
76+static
77+int cmp_dbRecordNode(const ELLNODE *lhs, const ELLNODE *rhs)
78+{
79+ dbRecordNode *LHS = (dbRecordNode*)lhs,
80+ *RHS = (dbRecordNode*)rhs;
81+
82+ return strcmp(LHS->recordname, RHS->recordname);
83+}
84+
85 static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
86 const char *path,const char *substitutions)
87 {
88@@ -295,6 +304,15 @@
89 dbFinishEntry(pdbEntry);
90 }
91 cleanup:
92+ {
93+ ELLNODE *cur;
94+ for(cur = ellFirst(&pdbbase->recordTypeList); cur; cur=ellNext(cur))
95+ {
96+ dbRecordType *rtype = CONTAINER(cur, dbRecordType, node);
97+
98+ ellSortStable(&rtype->recList, &cmp_dbRecordNode);
99+ }
100+ }
101 if(macHandle) macDeleteHandle(macHandle);
102 macHandle = NULL;
103 if(mac_input_buffer) free((void *)mac_input_buffer);
104
105=== modified file 'src/ioc/dbStatic/dbStaticLib.c'
106--- src/ioc/dbStatic/dbStaticLib.c 2016-03-30 17:39:36 +0000
107+++ src/ioc/dbStatic/dbStaticLib.c 2016-04-27 12:02:50 +0000
108@@ -1397,7 +1397,7 @@
109 /*Get size of NAME field*/
110 pdbFldDes = precordType->papFldDes[0];
111 if(!pdbFldDes || (strcmp(pdbFldDes->name,"NAME")!=0))
112- return(S_dbLib_nameLength);
113+ return(S_dbLib_nameLength);
114 if((int)strlen(precordName)>=pdbFldDes->size) return(S_dbLib_nameLength);
115 /* clear callers entry */
116 zeroDbentry(pdbentry);
117@@ -1412,18 +1412,7 @@
118 if((status = dbAllocRecord(pdbentry,precordName))) return(status);
119 pNewRecNode->recordname = dbRecordName(pdbentry);
120 ellInit(&pNewRecNode->infoList);
121- /* install record node in list in sorted postion */
122- status = dbFirstRecord(pdbentry);
123- while(status==0) {
124- if(strcmp(precordName,dbGetRecordName(pdbentry)) < 0) break;
125- status = dbNextRecord(pdbentry);
126- }
127- if(status==0) {
128- precnode = pdbentry->precnode;
129- ellInsert(preclist,ellPrevious(&precnode->node),&pNewRecNode->node);
130- } else {
131- ellAdd(preclist,&pNewRecNode->node);
132- }
133+ ellAdd(preclist, &pNewRecNode->node);
134 pdbentry->precnode = pNewRecNode;
135 ppvd = dbPvdAdd(pdbentry->pdbbase,precordType,pNewRecNode);
136 if(!ppvd) {errMessage(-1,"Logic Err: Could not add to PVD");return(-1);}
137
138=== modified file 'src/libCom/ellLib/Makefile'
139--- src/libCom/ellLib/Makefile 2011-02-25 21:39:44 +0000
140+++ src/libCom/ellLib/Makefile 2016-04-27 12:02:50 +0000
141@@ -10,3 +10,4 @@
142 SRC_DIRS += $(LIBCOM)/ellLib
143 INC += ellLib.h
144 Com_SRCS += ellLib.c
145+Com_SRCS += ellSort.c
146
147=== modified file 'src/libCom/ellLib/ellLib.h'
148--- src/libCom/ellLib/ellLib.h 2012-04-27 17:21:57 +0000
149+++ src/libCom/ellLib/ellLib.h 2016-04-27 12:02:50 +0000
150@@ -58,6 +58,8 @@
151 epicsShareFunc ELLNODE * ellNth (ELLLIST *pList, int nodeNum);
152 epicsShareFunc ELLNODE * ellNStep (ELLNODE *pNode, int nStep);
153 epicsShareFunc int ellFind (ELLLIST *pList, ELLNODE *pNode);
154+typedef int (*pListCmp)(ELLNODE* A, ELLNODE* B);
155+epicsShareFunc void ellSortStable(ELLLIST *pList, pListCmp);
156 epicsShareFunc void ellFree2 (ELLLIST *pList, FREEFUNC freeFunc);
157 epicsShareFunc void ellVerify (ELLLIST *pList);
158
159
160=== added file 'src/libCom/ellLib/ellSort.c'
161--- src/libCom/ellLib/ellSort.c 1970-01-01 00:00:00 +0000
162+++ src/libCom/ellLib/ellSort.c 2016-04-27 12:02:50 +0000
163@@ -0,0 +1,83 @@
164+/*************************************************************************\
165+* Copyright (c) 2014 Brookhaven Science Assoc., as Operator of Argonne
166+* National Laboratory.
167+* Copyright (c) 2016 Michael Davidsaver
168+* EPICS BASE is distributed subject to a Software License Agreement found
169+* in file LICENSE that is included with this distribution.
170+\*************************************************************************/
171+
172+/*
173+ * Use of mergesort algorithm based on analysis by
174+ * http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
175+ */
176+#include <stdlib.h>
177+
178+#define epicsExportSharedSymbols
179+#include "epicsAssert.h"
180+#include "ellLib.h"
181+
182+static void ellMoveN(ELLLIST* pTo, ELLLIST* pFrom, int count )
183+{
184+ for(;count && ellCount(pFrom); count--) {
185+ ELLNODE *node = ellGet(pFrom);
186+ ellAdd(pTo, node);
187+ }
188+}
189+
190+/* Stable (MergeSort) to given list.
191+ * The comparison function cmp(A,B) is expected
192+ * to return -1 for A<B, 0 for A==B, and 1 for A>B.
193+ */
194+void ellSortStable(ELLLIST *pList, pListCmp cmp)
195+{
196+ ELLLIST INP, P, Q;
197+ size_t insize = 1; /* initial sub-list size */
198+ if(ellCount(pList)<=1)
199+ return;
200+
201+ ellInit(&INP);
202+ ellInit(&P);
203+ ellInit(&Q);
204+
205+ /* Process is to iteratively sort
206+ * a sequence of sub-lists of size 'insize'
207+ */
208+
209+ while(insize < ellCount(pList)) {
210+
211+ assert(ellCount(&INP)==0);
212+
213+ /* shift previous results to inputs */
214+ ellConcat(&INP, pList);
215+
216+ while(ellCount(&INP))
217+ {
218+ ELLNODE *p, *q;
219+
220+ /* Pull out the next pair of sub-lists */
221+ ellMoveN(&Q, &INP, insize);
222+ ellMoveN(&P, &INP, insize);
223+
224+ /* merge these sub-lists */
225+ while((p=ellFirst(&P)) && (q=ellFirst(&Q)))
226+ {
227+ if((*cmp)(p,q) < 0) {
228+ ellAdd(pList, ellGet(&P));
229+ } else {
230+ ellAdd(pList, ellGet(&Q));
231+ }
232+ }
233+
234+ /* concatenate any remaining to result */
235+ if(ellFirst(&P))
236+ ellConcat(pList, &P);
237+ else if(ellFirst(&Q))
238+ ellConcat(pList, &Q);
239+
240+ assert(!ellFirst(&P) && !ellFirst(&Q));
241+ }
242+
243+ insize *= 2;
244+ }
245+
246+}
247
248=== modified file 'src/libCom/test/epicsEllTest.c'
249--- src/libCom/test/epicsEllTest.c 2009-08-28 18:34:38 +0000
250+++ src/libCom/test/epicsEllTest.c 2016-04-27 12:02:50 +0000
251@@ -1,4 +1,5 @@
252 /*************************************************************************\
253+* Copyright (c) 2016 Michael Davidsaver
254 * Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne
255 * National Laboratory.
256 * Copyright (c) 2002 The Regents of the University of California, as
257@@ -11,6 +12,7 @@
258 #include <stdlib.h>
259
260 #include "ellLib.h"
261+#include "dbDefs.h"
262 #include "epicsUnitTest.h"
263 #include "testMain.h"
264
265@@ -20,15 +22,13 @@
266 int num;
267 };
268
269-MAIN(epicsEllTest)
270+static void testList(void)
271 {
272 ELLLIST list1;
273 ELLLIST list2 = ELLLIST_INIT;
274 int i1 = 1;
275 struct myItem *pitem, *pfirst, *pick;
276
277- testPlan(70);
278-
279 list1.count = 27;
280 list1.node.next = (ELLNODE *) 0x01010101;
281 list1.node.previous = (ELLNODE *) 0x10101010;
282@@ -192,6 +192,77 @@
283
284 ellFree2(&list1, free);
285 testOk1(ellCount(&list1) == 0);
286+}
287+
288+typedef struct { int A, B; } input_t;
289+
290+static int myItemCmp(ELLNODE *a, ELLNODE *b)
291+{
292+ struct myItem *A = CONTAINER(a, struct myItem, node),
293+ *B = CONTAINER(b, struct myItem, node);
294+
295+ if (A->num < B->num) return -1;
296+ else if(A->num > B->num) return 1;
297+ else if(A->list < B->list) return -1;
298+ else if(A->list > B->list) return 1;
299+ else return 0;
300+}
301+
302+static const input_t input[] = {
303+ {-4, 0},
304+ {-5, 0},
305+ {0,0},
306+ {50,0},
307+ {0,1},
308+ {5,0},
309+ {5,1}
310+};
311+
312+static
313+void testSort(const input_t *inp, size_t ninp)
314+{
315+ unsigned i;
316+ ELLLIST list = ELLLIST_INIT;
317+ struct myItem *alloc = calloc(ninp, sizeof(*alloc));
318+
319+ if(!alloc) testAbort("testSort allocation fails");
320+
321+ for(i=0; i<ninp; i++) {
322+ struct myItem *it = &alloc[i];
323+
324+ it->num = inp[i].A;
325+ it->list= inp[i].B;
326+
327+ ellAdd(&list, &it->node);
328+ }
329+
330+ ellSortStable(&list, &myItemCmp);
331+
332+ testOk(ellCount(&list)==ninp, "output length %u == %u", (unsigned)ellCount(&list), (unsigned)ninp);
333+ if(ellCount(&list)==0) {
334+ testSkip(ninp-1, "all items lost");
335+ }
336+
337+ {
338+ struct myItem *prev = CONTAINER(ellFirst(&list), struct myItem, node),
339+ *next;
340+
341+ for(next = CONTAINER(ellNext(&prev->node), struct myItem, node);
342+ next;
343+ prev = next, next = CONTAINER(ellNext(&next->node), struct myItem, node))
344+ {
345+ int cond = (prev->num<next->num) || (prev->num==next->num && prev->list<next->list);
346+ testOk(cond, "%d:%d < %d:%d", prev->num, prev->list, next->num, next->list);
347+ }
348+ }
349+}
350+
351+MAIN(epicsEllTest)
352+{
353+ testPlan(77);
354+
355+ testList();
356+ testSort(input, NELEMENTS(input));
357
358 return testDone();
359 }

Subscribers

People subscribed via source and target branches