Merge ~bfrk/epics-base:scalar-get-optimization into epics-base:7.0

Proposed by Ben Franksen
Status: Needs review
Proposed branch: ~bfrk/epics-base:scalar-get-optimization
Merge into: epics-base:7.0
Diff against target: 230 lines (+72/-55)
4 files modified
modules/database/src/ioc/db/dbChannel.c (+53/-4)
modules/database/src/ioc/db/dbChannel.h (+7/-2)
modules/database/src/ioc/db/dbDbLink.c (+12/-46)
modules/database/src/ioc/dbStatic/link.h (+0/-3)
Reviewer Review Type Date Requested Status
Ben Franksen Pending
EPICS Core Developers Pending
Review via email: mp+399344@code.launchpad.net

This proposal supersedes a proposal from 2020-04-14.

Commit message

rebased to current 7.0 and removed the dependency on the (already merged) remove-dbfl_type_rec

Description of the change

The idea here is to make the special optimization for one-element get requests without options available to all code that calls dbChannelGet. It started out as an attempt to disentangle this logic from dbDbGetValue (this goal is indeed achieved) and then moved on from that point.

To post a comment you must log in.
Revision history for this message
Ben Franksen (bfrk) wrote : Posted in a previous version of this proposal

I light of the bug I introduced with 3f13fff I will rebase this and the other branches I proposed for merging.

Revision history for this message
mdavidsaver (mdavidsaver) wrote : Posted in a previous version of this proposal

Do you consider this MR ready? If so, please make an Approve review yourself. Or Needs Fixing if you have additional work planned.

wrt. changing the signature of dbGetField(), what is your plan for handling API compatibility with existing out of tree code? A quick search doesn't find as many usages as I was expecting, but there are some (eg. caPutLog)

https://github.com/search?q=org%3Aepics-modules+dbGetField&type=Code

I've a similar question about dbChannelGet*(), although this should have an easier answer.

In terms of the content, I'm not so enthusiastic about bulking up dbChannelGet() which has so far been a thin wrapper around dbGet(). Why can't this optimization be applied to dbGet()?

If this optimization to dbChannelGet() is judged worthwhile, some test coverage will be necessary.

Revision history for this message
Ben Franksen (bfrk) wrote : Posted in a previous version of this proposal

I have re-submitted with an updated dependency. The type signature of dbGetField is unchanged. the change in the type signature for dbChannelGet merely makes the type of the last parameter more strongly typed. If some client passed something that is not a db_field_log* it would be an error.

Dirk has shown with a simple benchmark that the optimization is worthwhile for DB links, as Andrew had previously suspected. See the diuscussion starting at https://code.launchpad.net/~dirk.zimoch/epics-base/+git/epics-base/+merge/378968/comments/1000328

I deliberately did not move it to dbGet because dbGet with its multiple case distinctions is already quite a complicated beast. Entangling the optimization with that code would have been counter-productive. My goal was to disentangle it and put it in a single place, which is precisely why I chose dbChannelGet which, as you observed, was previously just a simple wrapper for dbGet.

review: Approve
Revision history for this message
Ben Franksen (bfrk) wrote : Posted in a previous version of this proposal

Rebased. Also, the conditions under which the scalar get optimization applies now states them positively, moving the un-optimized cases into the else branch. This is clearer and more in line with the comments.

Unmerged commits

95da3b0... by Ben Franksen

move optimization for scalar requests from link to channel

The optimization is now available for all dbChannels, instead of only for DB
links, and also for requests involving filters. The code more clearly points
out where we optimize this special case and under what conditions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c
2index c71d842..c1d996f 100644
3--- a/modules/database/src/ioc/db/dbChannel.c
4+++ b/modules/database/src/ioc/db/dbChannel.c
5@@ -34,10 +34,12 @@
6 #include "dbBase.h"
7 #include "dbChannel.h"
8 #include "dbCommon.h"
9+#include "dbConvertFast.h"
10 #include "dbEvent.h"
11 #include "dbLock.h"
12 #include "dbStaticLib.h"
13 #include "link.h"
14+#include "recGbl.h"
15 #include "recSup.h"
16 #include "special.h"
17 #include "alarm.h"
18@@ -630,14 +632,61 @@ long dbChannelOpen(dbChannel *chan)
19 }
20
21 /* Only use dbChannelGet() if the record is already locked. */
22-long dbChannelGet(dbChannel *chan, short type, void *pbuffer,
23- long *options, long *nRequest, void *pfl)
24+long dbChannelGet(dbChannel *chan, short dbrType, void *pbuffer,
25+ long *options, long *nRequest, db_field_log *pfl)
26 {
27- return dbGet(&chan->addr, type, pbuffer, options, nRequest, pfl);
28+ dbAddr addr = chan->addr; /* structure copy */
29+
30+ if (pfl) {
31+ addr.field_size = pfl->field_size;
32+ addr.field_type = pfl->field_type;
33+ addr.no_elements = pfl->no_elements;
34+ addr.pfield = dbfl_pfield(pfl);
35+ }
36+
37+ /*
38+ * Try to optimize scalar requests by caching (just) the conversion
39+ * routine. This is possible only if we have no options, the field and the
40+ * request have exactly one element, we have no special processing to do,
41+ * and the field type is not DBF_DEVICE or larger.
42+ * Note that if the db_field_log has already copied the data, dbGet
43+ * does not call get_array_info.
44+ */
45+ if (!(options && *options)
46+ && addr.no_elements == 1
47+ && (!nRequest || *nRequest == 1)
48+ && (dbfl_has_copy(pfl) || addr.special != SPC_DBADDR)
49+ && addr.special != SPC_ATTRIBUTE
50+ && addr.field_type <= DBF_DEVICE)
51+ {
52+ if (chan->getCvt && chan->lastGetdbrType == dbrType) {
53+ return chan->getCvt(addr.pfield, pbuffer, &addr);
54+ } else {
55+ unsigned short dbfType = addr.field_type;
56+
57+ if (INVALID_DB_REQ(dbrType)) {
58+ char message[80];
59+
60+ sprintf(message, "dbChannelGet: Request type is %d\n", dbrType);
61+ recGblDbaddrError(S_db_badDbrtype, &addr, message);
62+ return S_db_badDbrtype;
63+ }
64+
65+ chan->getCvt = dbFastGetConvertRoutine[dbfType][dbrType];
66+ chan->lastGetdbrType = dbrType;
67+ if (nRequest)
68+ *nRequest = addr.no_elements;
69+ return chan->getCvt(addr.pfield, pbuffer, &addr);
70+ }
71+ } else {
72+ /* non-optimized case */
73+ chan->getCvt = NULL;
74+ return dbGet(&addr, dbrType, pbuffer, options, nRequest, pfl);
75+ }
76 }
77
78 long dbChannelGetField(dbChannel *chan, short dbrType, void *pbuffer,
79- long *options, long *nRequest, void *pfl)
80+ long *options, long *nRequest, db_field_log *pfl)
81 {
82 dbCommon *precord = chan->addr.precord;
83 long status = 0;
84diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h
85index ec86e9e..dc6240f 100644
86--- a/modules/database/src/ioc/db/dbChannel.h
87+++ b/modules/database/src/ioc/db/dbChannel.h
88@@ -50,6 +50,8 @@ typedef struct evSubscrip {
89
90 typedef struct chFilter chFilter;
91
92+typedef long fastConvert(void *from, void *to, const dbAddr *paddr);
93+
94 /* A dbChannel points to a record field, and can have multiple filters */
95 typedef struct dbChannel {
96 const char *name;
97@@ -60,6 +62,9 @@ typedef struct dbChannel {
98 ELLLIST filters; /* list of filters as created from JSON */
99 ELLLIST pre_chain; /* list of filters to be called pre-event-queue */
100 ELLLIST post_chain; /* list of filters to be called post-event-queue */
101+ /* Support for optimizing scalar requests */
102+ fastConvert *getCvt; /* fast get conversion function */
103+ short lastGetdbrType; /* last dbChannelGet dbrType */
104 } dbChannel;
105
106 /* Prototype for the channel event function that is called in filter stacks
107@@ -208,9 +213,9 @@ DBCORE_API extern unsigned short dbDBRnewToDBRold[];
108
109
110 DBCORE_API long dbChannelGet(dbChannel *chan, short type,
111- void *pbuffer, long *options, long *nRequest, void *pfl);
112+ void *pbuffer, long *options, long *nRequest, db_field_log *pfl);
113 DBCORE_API long dbChannelGetField(dbChannel *chan, short type,
114- void *pbuffer, long *options, long *nRequest, void *pfl);
115+ void *pbuffer, long *options, long *nRequest, db_field_log *pfl);
116 DBCORE_API long dbChannelPut(dbChannel *chan, short type,
117 const void *pbuffer, long nRequest);
118 DBCORE_API long dbChannelPutField(dbChannel *chan, short type,
119diff --git a/modules/database/src/ioc/db/dbDbLink.c b/modules/database/src/ioc/db/dbDbLink.c
120index b281314..399d36d 100644
121--- a/modules/database/src/ioc/db/dbDbLink.c
122+++ b/modules/database/src/ioc/db/dbDbLink.c
123@@ -58,7 +58,6 @@
124 #include "dbBase.h"
125 #include "dbBkpt.h"
126 #include "dbCommonPvt.h"
127-#include "dbConvertFast.h"
128 #include "dbConvert.h"
129 #include "db_field_log.h"
130 #include "db_access_routines.h"
131@@ -135,9 +134,7 @@ static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink)
132 /* locker is NULL when an isolated IOC is closing its links */
133 if (locker) {
134 plink->value.pv_link.pvt = 0;
135- plink->value.pv_link.getCvt = 0;
136 plink->value.pv_link.pvlMask = 0;
137- plink->value.pv_link.lastGetdbrType = 0;
138 ellDelete(&precord->bklnk, &plink->value.pv_link.backlinknode);
139 dbLockSetSplit(locker, plink->precord, precord);
140 }
141@@ -167,7 +164,6 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer,
142 {
143 struct pv_link *ppv_link = &plink->value.pv_link;
144 dbChannel *chan = linkChannel(plink);
145- DBADDR *paddr = &chan->addr;
146 dbCommon *precord = plink->precord;
147 db_field_log *pfl = NULL;
148 long status;
149@@ -179,49 +175,19 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer,
150 return status;
151 }
152
153- if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType)
154- {
155- /* shortcut: scalar with known conversion, no filter */
156- status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr);
157- }
158- else if (dbChannelFinalElements(chan) == 1 && (!pnRequest || *pnRequest == 1)
159- && dbChannelSpecial(chan) != SPC_DBADDR
160- && dbChannelSpecial(chan) != SPC_ATTRIBUTE
161- && ellCount(&chan->filters) == 0)
162- {
163- /* simple scalar: set up shortcut */
164- unsigned short dbfType = dbChannelFinalFieldType(chan);
165-
166- if (dbrType < 0 || dbrType > DBR_ENUM || dbfType > DBF_DEVICE)
167- return S_db_badDbrtype;
168-
169- ppv_link->getCvt = dbFastGetConvertRoutine[dbfType][dbrType];
170- ppv_link->lastGetdbrType = dbrType;
171- status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr);
172- }
173- else
174- {
175- /* filter, array, or special */
176- ppv_link->getCvt = NULL;
177-
178- if (ellCount(&chan->filters)) {
179- /* If filters are involved in a read, create field log and run filters */
180- pfl = db_create_read_log(chan);
181- if (!pfl)
182- return S_db_noMemory;
183-
184- pfl = dbChannelRunPreChain(chan, pfl);
185- pfl = dbChannelRunPostChain(chan, pfl);
186- }
187-
188- status = dbChannelGet(chan, dbrType, pbuffer, NULL, pnRequest, pfl);
189-
190- if (pfl)
191- db_delete_field_log(pfl);
192-
193- if (status)
194- return status;
195+ /* If filters are involved in a read, create field log and run filters */
196+ if (ellCount(&chan->filters)) {
197+ pfl = db_create_read_log(chan);
198+ if (!pfl)
199+ return S_db_noMemory;
200+ pfl = dbChannelRunPreChain(chan, pfl);
201+ pfl = dbChannelRunPostChain(chan, pfl);
202 }
203+ status = dbChannelGet(chan, dbrType, pbuffer, NULL, pnRequest, pfl);
204+ if (pfl)
205+ db_delete_field_log(pfl);
206+ if (status)
207+ return status;
208
209 if (!status && precord != dbChannelRecord(chan))
210 recGblInheritSevr(plink->value.pv_link.pvlMask & pvlOptMsMode,
211diff --git a/modules/database/src/ioc/dbStatic/link.h b/modules/database/src/ioc/dbStatic/link.h
212index c3c0045..d212395 100644
213--- a/modules/database/src/ioc/dbStatic/link.h
214+++ b/modules/database/src/ioc/dbStatic/link.h
215@@ -78,15 +78,12 @@ struct macro_link {
216 };
217
218 struct dbCommon;
219-typedef long (*LINKCVT)();
220
221 struct pv_link {
222 ELLNODE backlinknode;
223 char *pvname; /* pvname link points to */
224 void *pvt; /* CA or DB private */
225- LINKCVT getCvt; /* input conversion function */
226 short pvlMask; /* Options mask */
227- short lastGetdbrType; /* last dbrType for DB or CA get */
228 };
229
230 struct jlink;

Subscribers

People subscribed via source and target branches