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

Proposed by Ben Franksen
Status: Superseded
Proposed branch: ~bfrk/epics-base:scalar-get-optimization
Merge into: ~epics-core/epics-base/+git/epics-base:7.0
Prerequisite: ~bfrk/epics-base:remove-dbfl_type_rec
Diff against target: 218 lines (+64/-55)
4 files modified
modules/database/src/ioc/db/dbChannel.c (+45/-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 (community) Approve
EPICS Core Developers Pending
Review via email: mp+382204@code.launchpad.net

This proposal supersedes a proposal from 2020-03-27.

This proposal has been superseded by a proposal from 2021-03-09.

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 :

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 :

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

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

Subscribers

People subscribed via source and target branches