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

Proposed by Ben Franksen
Status: Needs review
Proposed branch: ~bfrk/epics-base:scalar-get-optimization
Merge into: ~epics-core/epics-base/+git/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
diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c
index c71d842..c1d996f 100644
--- a/modules/database/src/ioc/db/dbChannel.c
+++ b/modules/database/src/ioc/db/dbChannel.c
@@ -34,10 +34,12 @@
34#include "dbBase.h"34#include "dbBase.h"
35#include "dbChannel.h"35#include "dbChannel.h"
36#include "dbCommon.h"36#include "dbCommon.h"
37#include "dbConvertFast.h"
37#include "dbEvent.h"38#include "dbEvent.h"
38#include "dbLock.h"39#include "dbLock.h"
39#include "dbStaticLib.h"40#include "dbStaticLib.h"
40#include "link.h"41#include "link.h"
42#include "recGbl.h"
41#include "recSup.h"43#include "recSup.h"
42#include "special.h"44#include "special.h"
43#include "alarm.h"45#include "alarm.h"
@@ -630,14 +632,61 @@ long dbChannelOpen(dbChannel *chan)
630}632}
631633
632/* Only use dbChannelGet() if the record is already locked. */634/* Only use dbChannelGet() if the record is already locked. */
633long dbChannelGet(dbChannel *chan, short type, void *pbuffer,635long dbChannelGet(dbChannel *chan, short dbrType, void *pbuffer,
634 long *options, long *nRequest, void *pfl)636 long *options, long *nRequest, db_field_log *pfl)
635{637{
636 return dbGet(&chan->addr, type, pbuffer, options, nRequest, pfl);638 dbAddr addr = chan->addr; /* structure copy */
639
640 if (pfl) {
641 addr.field_size = pfl->field_size;
642 addr.field_type = pfl->field_type;
643 addr.no_elements = pfl->no_elements;
644 addr.pfield = dbfl_pfield(pfl);
645 }
646
647 /*
648 * Try to optimize scalar requests by caching (just) the conversion
649 * routine. This is possible only if we have no options, the field and the
650 * request have exactly one element, we have no special processing to do,
651 * and the field type is not DBF_DEVICE or larger.
652 * Note that if the db_field_log has already copied the data, dbGet
653 * does not call get_array_info.
654 */
655 if (!(options && *options)
656 && addr.no_elements == 1
657 && (!nRequest || *nRequest == 1)
658 && (dbfl_has_copy(pfl) || addr.special != SPC_DBADDR)
659 && addr.special != SPC_ATTRIBUTE
660 && addr.field_type <= DBF_DEVICE)
661 {
662 if (chan->getCvt && chan->lastGetdbrType == dbrType) {
663 return chan->getCvt(addr.pfield, pbuffer, &addr);
664 } else {
665 unsigned short dbfType = addr.field_type;
666
667 if (INVALID_DB_REQ(dbrType)) {
668 char message[80];
669
670 sprintf(message, "dbChannelGet: Request type is %d\n", dbrType);
671 recGblDbaddrError(S_db_badDbrtype, &addr, message);
672 return S_db_badDbrtype;
673 }
674
675 chan->getCvt = dbFastGetConvertRoutine[dbfType][dbrType];
676 chan->lastGetdbrType = dbrType;
677 if (nRequest)
678 *nRequest = addr.no_elements;
679 return chan->getCvt(addr.pfield, pbuffer, &addr);
680 }
681 } else {
682 /* non-optimized case */
683 chan->getCvt = NULL;
684 return dbGet(&addr, dbrType, pbuffer, options, nRequest, pfl);
685 }
637}686}
638687
639long dbChannelGetField(dbChannel *chan, short dbrType, void *pbuffer,688long dbChannelGetField(dbChannel *chan, short dbrType, void *pbuffer,
640 long *options, long *nRequest, void *pfl)689 long *options, long *nRequest, db_field_log *pfl)
641{690{
642 dbCommon *precord = chan->addr.precord;691 dbCommon *precord = chan->addr.precord;
643 long status = 0;692 long status = 0;
diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h
index ec86e9e..dc6240f 100644
--- a/modules/database/src/ioc/db/dbChannel.h
+++ b/modules/database/src/ioc/db/dbChannel.h
@@ -50,6 +50,8 @@ typedef struct evSubscrip {
5050
51typedef struct chFilter chFilter;51typedef struct chFilter chFilter;
5252
53typedef long fastConvert(void *from, void *to, const dbAddr *paddr);
54
53/* A dbChannel points to a record field, and can have multiple filters */55/* A dbChannel points to a record field, and can have multiple filters */
54typedef struct dbChannel {56typedef struct dbChannel {
55 const char *name;57 const char *name;
@@ -60,6 +62,9 @@ typedef struct dbChannel {
60 ELLLIST filters; /* list of filters as created from JSON */62 ELLLIST filters; /* list of filters as created from JSON */
61 ELLLIST pre_chain; /* list of filters to be called pre-event-queue */63 ELLLIST pre_chain; /* list of filters to be called pre-event-queue */
62 ELLLIST post_chain; /* list of filters to be called post-event-queue */64 ELLLIST post_chain; /* list of filters to be called post-event-queue */
65 /* Support for optimizing scalar requests */
66 fastConvert *getCvt; /* fast get conversion function */
67 short lastGetdbrType; /* last dbChannelGet dbrType */
63} dbChannel;68} dbChannel;
6469
65/* Prototype for the channel event function that is called in filter stacks70/* Prototype for the channel event function that is called in filter stacks
@@ -208,9 +213,9 @@ DBCORE_API extern unsigned short dbDBRnewToDBRold[];
208213
209214
210DBCORE_API long dbChannelGet(dbChannel *chan, short type,215DBCORE_API long dbChannelGet(dbChannel *chan, short type,
211 void *pbuffer, long *options, long *nRequest, void *pfl);216 void *pbuffer, long *options, long *nRequest, db_field_log *pfl);
212DBCORE_API long dbChannelGetField(dbChannel *chan, short type,217DBCORE_API long dbChannelGetField(dbChannel *chan, short type,
213 void *pbuffer, long *options, long *nRequest, void *pfl);218 void *pbuffer, long *options, long *nRequest, db_field_log *pfl);
214DBCORE_API long dbChannelPut(dbChannel *chan, short type,219DBCORE_API long dbChannelPut(dbChannel *chan, short type,
215 const void *pbuffer, long nRequest);220 const void *pbuffer, long nRequest);
216DBCORE_API long dbChannelPutField(dbChannel *chan, short type,221DBCORE_API long dbChannelPutField(dbChannel *chan, short type,
diff --git a/modules/database/src/ioc/db/dbDbLink.c b/modules/database/src/ioc/db/dbDbLink.c
index b281314..399d36d 100644
--- a/modules/database/src/ioc/db/dbDbLink.c
+++ b/modules/database/src/ioc/db/dbDbLink.c
@@ -58,7 +58,6 @@
58#include "dbBase.h"58#include "dbBase.h"
59#include "dbBkpt.h"59#include "dbBkpt.h"
60#include "dbCommonPvt.h"60#include "dbCommonPvt.h"
61#include "dbConvertFast.h"
62#include "dbConvert.h"61#include "dbConvert.h"
63#include "db_field_log.h"62#include "db_field_log.h"
64#include "db_access_routines.h"63#include "db_access_routines.h"
@@ -135,9 +134,7 @@ static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink)
135 /* locker is NULL when an isolated IOC is closing its links */134 /* locker is NULL when an isolated IOC is closing its links */
136 if (locker) {135 if (locker) {
137 plink->value.pv_link.pvt = 0;136 plink->value.pv_link.pvt = 0;
138 plink->value.pv_link.getCvt = 0;
139 plink->value.pv_link.pvlMask = 0;137 plink->value.pv_link.pvlMask = 0;
140 plink->value.pv_link.lastGetdbrType = 0;
141 ellDelete(&precord->bklnk, &plink->value.pv_link.backlinknode);138 ellDelete(&precord->bklnk, &plink->value.pv_link.backlinknode);
142 dbLockSetSplit(locker, plink->precord, precord);139 dbLockSetSplit(locker, plink->precord, precord);
143 }140 }
@@ -167,7 +164,6 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer,
167{164{
168 struct pv_link *ppv_link = &plink->value.pv_link;165 struct pv_link *ppv_link = &plink->value.pv_link;
169 dbChannel *chan = linkChannel(plink);166 dbChannel *chan = linkChannel(plink);
170 DBADDR *paddr = &chan->addr;
171 dbCommon *precord = plink->precord;167 dbCommon *precord = plink->precord;
172 db_field_log *pfl = NULL;168 db_field_log *pfl = NULL;
173 long status;169 long status;
@@ -179,49 +175,19 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer,
179 return status;175 return status;
180 }176 }
181177
182 if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType)178 /* If filters are involved in a read, create field log and run filters */
183 {179 if (ellCount(&chan->filters)) {
184 /* shortcut: scalar with known conversion, no filter */180 pfl = db_create_read_log(chan);
185 status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr);181 if (!pfl)
186 }182 return S_db_noMemory;
187 else if (dbChannelFinalElements(chan) == 1 && (!pnRequest || *pnRequest == 1)183 pfl = dbChannelRunPreChain(chan, pfl);
188 && dbChannelSpecial(chan) != SPC_DBADDR184 pfl = dbChannelRunPostChain(chan, pfl);
189 && dbChannelSpecial(chan) != SPC_ATTRIBUTE
190 && ellCount(&chan->filters) == 0)
191 {
192 /* simple scalar: set up shortcut */
193 unsigned short dbfType = dbChannelFinalFieldType(chan);
194
195 if (dbrType < 0 || dbrType > DBR_ENUM || dbfType > DBF_DEVICE)
196 return S_db_badDbrtype;
197
198 ppv_link->getCvt = dbFastGetConvertRoutine[dbfType][dbrType];
199 ppv_link->lastGetdbrType = dbrType;
200 status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr);
201 }
202 else
203 {
204 /* filter, array, or special */
205 ppv_link->getCvt = NULL;
206
207 if (ellCount(&chan->filters)) {
208 /* If filters are involved in a read, create field log and run filters */
209 pfl = db_create_read_log(chan);
210 if (!pfl)
211 return S_db_noMemory;
212
213 pfl = dbChannelRunPreChain(chan, pfl);
214 pfl = dbChannelRunPostChain(chan, pfl);
215 }
216
217 status = dbChannelGet(chan, dbrType, pbuffer, NULL, pnRequest, pfl);
218
219 if (pfl)
220 db_delete_field_log(pfl);
221
222 if (status)
223 return status;
224 }185 }
186 status = dbChannelGet(chan, dbrType, pbuffer, NULL, pnRequest, pfl);
187 if (pfl)
188 db_delete_field_log(pfl);
189 if (status)
190 return status;
225191
226 if (!status && precord != dbChannelRecord(chan))192 if (!status && precord != dbChannelRecord(chan))
227 recGblInheritSevr(plink->value.pv_link.pvlMask & pvlOptMsMode,193 recGblInheritSevr(plink->value.pv_link.pvlMask & pvlOptMsMode,
diff --git a/modules/database/src/ioc/dbStatic/link.h b/modules/database/src/ioc/dbStatic/link.h
index c3c0045..d212395 100644
--- a/modules/database/src/ioc/dbStatic/link.h
+++ b/modules/database/src/ioc/dbStatic/link.h
@@ -78,15 +78,12 @@ struct macro_link {
78};78};
7979
80struct dbCommon;80struct dbCommon;
81typedef long (*LINKCVT)();
8281
83struct pv_link {82struct pv_link {
84 ELLNODE backlinknode;83 ELLNODE backlinknode;
85 char *pvname; /* pvname link points to */84 char *pvname; /* pvname link points to */
86 void *pvt; /* CA or DB private */85 void *pvt; /* CA or DB private */
87 LINKCVT getCvt; /* input conversion function */
88 short pvlMask; /* Options mask */86 short pvlMask; /* Options mask */
89 short lastGetdbrType; /* last dbrType for DB or CA get */
90};87};
9188
92struct jlink;89struct jlink;

Subscribers

People subscribed via source and target branches