Skip to content

Commit

Permalink
Merge branch 'bytecode_caching' into main
Browse files Browse the repository at this point in the history
Allows to compile Python code once and cache the compiled byte-code unless the
code changes in the record. Some of the advantages include allowing to skip the
record parameters conversion to string and later to Python type, with the conversion
gone so are problems with lost precision for double/float types, and as reported
the record processing the Python code could be as much as 5x faster.
  • Loading branch information
klemenv committed Jul 12, 2024
2 parents ec1ef37 + 4993404 commit a3488dd
Show file tree
Hide file tree
Showing 24 changed files with 1,315 additions and 801 deletions.
1 change: 1 addition & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pydev_SRCS += pydev_stringin.cpp
pydev_SRCS += pydev_stringout.cpp
pydev_SRCS += pydev_waveform.cpp
pydev_SRCS += pycalcRecord.cpp
pydev_SRCS += variant.cpp

# 3.15 and above support lsi, lso and printf records
ifdef BASE_3_15
Expand Down
171 changes: 105 additions & 66 deletions src/pycalcRecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ static long fetchValues(pycalcRecord *rec);
struct PyCalcRecordContext {
CALLBACK callback;
int processCbStatus;
std::string code;
PyWrapper::ByteCode bytecode;
};

rset pycalcRSET = {
Expand Down Expand Up @@ -136,130 +138,167 @@ static long initRecord(dbCommon *common, int pass)

static void processRecordCb(pycalcRecord* rec)
{
auto fields = Util::getFields(rec->calc);
for (auto& keyval: fields) {
if (keyval.first == "NAME") keyval.second = rec->name;
else if (keyval.first == "TPRO") keyval.second = std::to_string(rec->tpro);
auto ctx = reinterpret_cast<PyCalcRecordContext*>(rec->ctx);
std::string code = rec->calc;
std::map<std::string, Variant> args;
for (auto& macro: Util::getMacros(code)) {
if (macro == "NAME") { args["pydevNAME"] = Variant(rec->name); code = Util::replaceMacro(code, "NAME", "pydevNAME"); }
else if (macro == "TPRO") { args["pydevTPRO"] = Variant(rec->tpro); code = Util::replaceMacro(code, "TPRO", "pydevTPRO"); }
else {
for (auto i = 0; i < PYCALCREC_NARGS; i++) {
std::string field = std::string(1,'A'+i);
if (keyval.first == field) {
if (macro == field) {
auto val = &rec->a + i;
auto ft = &rec->fta + i;
auto me = &rec->mea + i;
auto ne = &rec->nea + i;

code = Util::replaceMacro(code, field, "pydev"+field);
if (*me == 1) {
if (*ft == DBR_CHAR) keyval.second = std::to_string(*reinterpret_cast< epicsInt8*>(*val));
else if (*ft == DBR_UCHAR) keyval.second = std::to_string(*reinterpret_cast< epicsUInt8*>(*val));
else if (*ft == DBR_SHORT) keyval.second = std::to_string(*reinterpret_cast< epicsInt16*>(*val));
else if (*ft == DBR_USHORT) keyval.second = std::to_string(*reinterpret_cast< epicsUInt16*>(*val));
else if (*ft == DBR_LONG) keyval.second = std::to_string(*reinterpret_cast< epicsInt32*>(*val));
else if (*ft == DBR_ULONG) keyval.second = std::to_string(*reinterpret_cast< epicsUInt32*>(*val));
if (*ft == DBR_CHAR) { args["pydev"+field] = Variant(*reinterpret_cast< epicsInt8*>(*val)); }
else if (*ft == DBR_UCHAR) { args["pydev"+field] = Variant(*reinterpret_cast< epicsUInt8*>(*val)); }
else if (*ft == DBR_SHORT) { args["pydev"+field] = Variant(*reinterpret_cast< epicsInt16*>(*val)); }
else if (*ft == DBR_USHORT) { args["pydev"+field] = Variant(*reinterpret_cast< epicsUInt16*>(*val)); }
else if (*ft == DBR_LONG) { args["pydev"+field] = Variant(*reinterpret_cast< epicsInt32*>(*val)); }
else if (*ft == DBR_ULONG) { args["pydev"+field] = Variant(*reinterpret_cast< epicsUInt32*>(*val)); }
#ifdef HAVE_EPICS_INT64
else if (*ft == DBR_INT64) keyval.second = std::to_string(*reinterpret_cast< epicsInt64*>(*val));
else if (*ft == DBR_UINT64) keyval.second = std::to_string(*reinterpret_cast< epicsUInt64*>(*val));
else if (*ft == DBR_INT64) { args["pydev"+field] = Variant(*reinterpret_cast< epicsInt64*>(*val)); }
else if (*ft == DBR_UINT64) { args["pydev"+field] = Variant(*reinterpret_cast< epicsUInt64*>(*val)); }
#endif
else if (*ft == DBR_FLOAT) keyval.second = std::to_string(*reinterpret_cast<epicsFloat32*>(*val));
else if (*ft == DBR_DOUBLE) keyval.second = std::to_string(*reinterpret_cast<epicsFloat64*>(*val));
else if (*ft == DBR_STRING) keyval.second = std::string(reinterpret_cast<const char*>(*val));
else if (*ft == DBR_FLOAT) { args["pydev"+field] = Variant(*reinterpret_cast<epicsFloat32*>(*val)); }
else if (*ft == DBR_DOUBLE) { args["pydev"+field] = Variant(*reinterpret_cast<epicsFloat64*>(*val)); }
else if (*ft == DBR_STRING) { args["pydev"+field] = Variant(*reinterpret_cast< char*>(*val)); }
} else {
if (*ft == DBR_CHAR) keyval.second = Util::to_pylist_string( reinterpret_cast< epicsInt8*>(*val), *ne );
else if (*ft == DBR_UCHAR) keyval.second = Util::to_pylist_string( reinterpret_cast< epicsUInt8*>(*val), *ne );
else if (*ft == DBR_SHORT) keyval.second = Util::to_pylist_string( reinterpret_cast< epicsInt16*>(*val), *ne );
else if (*ft == DBR_USHORT) keyval.second = Util::to_pylist_string( reinterpret_cast< epicsUInt16*>(*val), *ne );
else if (*ft == DBR_LONG) keyval.second = Util::to_pylist_string( reinterpret_cast< epicsInt32*>(*val), *ne );
else if (*ft == DBR_ULONG) keyval.second = Util::to_pylist_string( reinterpret_cast< epicsUInt32*>(*val), *ne );
if (*ft == DBR_CHAR) { auto a = reinterpret_cast< epicsInt8*>(*val); args["pydev"+field] = Variant(a, *ne); }
else if (*ft == DBR_UCHAR) { auto a = reinterpret_cast< epicsUInt8*>(*val); args["pydev"+field] = Variant(a, *ne); }
else if (*ft == DBR_SHORT) { auto a = reinterpret_cast< epicsInt16*>(*val); args["pydev"+field] = Variant(a, *ne); }
else if (*ft == DBR_USHORT) { auto a = reinterpret_cast< epicsUInt16*>(*val); args["pydev"+field] = Variant(a, *ne); }
else if (*ft == DBR_LONG) { auto a = reinterpret_cast< epicsInt32*>(*val); args["pydev"+field] = Variant(a, *ne); }
else if (*ft == DBR_ULONG) { auto a = reinterpret_cast< epicsUInt32*>(*val); args["pydev"+field] = Variant(a, *ne); }
#ifdef HAVE_EPICS_INT64
else if (*ft == DBR_INT64) keyval.second = Util::to_pylist_string( reinterpret_cast< epicsInt64*>(*val), *ne );
else if (*ft == DBR_UINT64) keyval.second = Util::to_pylist_string( reinterpret_cast< epicsUInt64*>(*val), *ne );
else if (*ft == DBR_INT64) { auto a = reinterpret_cast< epicsInt64*>(*val); args["pydev"+field] = Variant(a, *ne); }
else if (*ft == DBR_UINT64) { auto a = reinterpret_cast< epicsUInt64*>(*val); args["pydev"+field] = Variant(a, *ne); }
#endif
else if (*ft == DBR_FLOAT) keyval.second = Util::to_pylist_string( reinterpret_cast<epicsFloat32*>(*val), *ne );
else if (*ft == DBR_DOUBLE) keyval.second = Util::to_pylist_string( reinterpret_cast<epicsFloat64*>(*val), *ne );
else if (*ft == DBR_FLOAT) { auto a = reinterpret_cast<epicsFloat32*>(*val); args["pydev"+field] = Variant(a, *ne); }
else if (*ft == DBR_DOUBLE) { auto a = reinterpret_cast<epicsFloat64*>(*val); args["pydev"+field] = Variant(a, *ne); }
else if (*ft == DBR_STRING) {
std::vector<std::string> a;
const char* ptr = reinterpret_cast<const char*>(*val);
for (size_t i=0; i<*ne; i++) {
std::string element(ptr, dbValueSize(DBF_STRING));
element.resize(element.find('\0'));
a.push_back("'" + Util::escape(element) + "'");
a.push_back(element);
ptr += dbValueSize(DBF_STRING);
}
keyval.second = Util::to_pylist_string(a);
args["pydev"+field] = Variant(a);
}
}
}
}
}
}
std::string code = Util::replaceFields(rec->calc, fields);

PyWrapper::MultiTypeValue ret;
long status = 0;
try {
ret = PyWrapper::exec(code, (rec->tpro == 1));
} catch (...) {
status = -1;
}
if (ctx->code != code) {
PyWrapper::destroy(std::move(ctx->bytecode));
ctx->bytecode = PyWrapper::compile(code, (rec->tpro == 1));
ctx->code = code;
}
auto ret = PyWrapper::eval(ctx->bytecode, args, (rec->tpro == 1));

rec->nevl = 0;
if (status == 0) {
rec->nevl = 0;
typedef long (*convertRoutineCast)(const void*, void*, void*);
if (ret.type == PyWrapper::MultiTypeValue::Type::BOOL) {
epicsInt32 l = ret.b;
if (ret.type == Variant::Type::BOOL) {
epicsInt32 val = ret.get_bool();
auto convert = reinterpret_cast<convertRoutineCast>(dbFastPutConvertRoutine[DBF_LONG][rec->ftvl]);
status = convert(&l, rec->val, 0);
if (convert(&val, rec->val, 0) != 0) {
throw Variant::ConvertError();
}
rec->nevl = 1;
} else if (ret.type == PyWrapper::MultiTypeValue::Type::INTEGER) {
} else if (ret.type == Variant::Type::LONG) {
epicsInt32 val = ret.get_long();
auto convert = reinterpret_cast<convertRoutineCast>(dbFastPutConvertRoutine[DBF_LONG][rec->ftvl]);
status = convert(&ret.i, rec->val, 0);
if (convert(&val, rec->val, 0) != 0) {
throw Variant::ConvertError();
}
rec->nevl = 1;
} else if (ret.type == PyWrapper::MultiTypeValue::Type::FLOAT) {
} else if (ret.type == Variant::Type::UNSIGNED) {
epicsInt32 val = ret.get_unsigned();
auto convert = reinterpret_cast<convertRoutineCast>(dbFastPutConvertRoutine[DBF_ULONG][rec->ftvl]);
if (convert(&val, rec->val, 0) != 0) {
throw Variant::ConvertError();
}
rec->nevl = 1;
} else if (ret.type == Variant::Type::DOUBLE) {
epicsFloat64 val = ret.get_double();
auto convert = reinterpret_cast<convertRoutineCast>(dbFastPutConvertRoutine[DBF_DOUBLE][rec->ftvl]);
status = convert(&ret.f, rec->val, 0);
if (convert(&val, rec->val, 0) != 0) {
throw Variant::ConvertError();
}
rec->nevl = 1;
} else if (ret.type == PyWrapper::MultiTypeValue::Type::STRING) {
char s[MAX_STRING_SIZE];
strncpy(s, ret.s.c_str(), MAX_STRING_SIZE);
s[MAX_STRING_SIZE-1] = 0;
} else if (ret.type == Variant::Type::STRING) {
char val[MAX_STRING_SIZE];
strncpy(val, ret.get_string().c_str(), MAX_STRING_SIZE);
val[MAX_STRING_SIZE-1] = 0;
auto convert = reinterpret_cast<convertRoutineCast>(dbFastPutConvertRoutine[DBF_STRING][rec->ftvl]);
status = convert(s, rec->val, 0);
if (convert(val, rec->val, 0) != 0) {
throw Variant::ConvertError();
}
rec->nevl = 1;
} else if (ret.type == PyWrapper::MultiTypeValue::Type::VECTOR_INTEGER) {
} else if (ret.type == Variant::Type::VECTOR_LONG) {
auto convert = reinterpret_cast<convertRoutineCast>(dbFastPutConvertRoutine[DBF_LONG][rec->ftvl]);
for (size_t i=0; i<ret.vi.size() && i<rec->mevl; i++) {
auto values = ret.get_long_array();
for (size_t i=0; i<values.size() && i<rec->mevl; i++) {
char* val = reinterpret_cast<char*>(rec->val) + i*dbValueSize(rec->ftvl);
status = convert(&ret.vi[i], val, 0);
if (status != 0) {
break;
if (convert(&values[i], val, 0) != 0) {
throw Variant::ConvertError();
}
rec->nevl++;
}
} else if (ret.type == PyWrapper::MultiTypeValue::Type::VECTOR_FLOAT) {
} else if (ret.type == Variant::Type::VECTOR_UNSIGNED) {
auto convert = reinterpret_cast<convertRoutineCast>(dbFastPutConvertRoutine[DBF_ULONG][rec->ftvl]);
auto values = ret.get_unsigned_array();
for (size_t i=0; i<values.size() && i<rec->mevl; i++) {
char* val = reinterpret_cast<char*>(rec->val) + i*dbValueSize(rec->ftvl);
if (convert(&values[i], val, 0) != 0) {
throw Variant::ConvertError();
}
rec->nevl++;
}
} else if (ret.type == Variant::Type::VECTOR_DOUBLE) {
auto convert = reinterpret_cast<convertRoutineCast>(dbFastPutConvertRoutine[DBF_DOUBLE][rec->ftvl]);
for (size_t i=0; i<ret.vf.size() && i<rec->mevl; i++) {
auto values = ret.get_double_array();
for (size_t i=0; i<values.size() && i<rec->mevl; i++) {
char* val = reinterpret_cast<char*>(rec->val) + i*dbValueSize(rec->ftvl);
status = convert(&ret.vf[i], val, 0);
if (status != 0) {
break;
if (convert(&values[i], val, 0) != 0) {
throw Variant::ConvertError();
}
rec->nevl++;
}
} else if (ret.type == PyWrapper::MultiTypeValue::Type::VECTOR_STRING) {
} else if (ret.type == Variant::Type::VECTOR_STRING) {
auto convert = reinterpret_cast<convertRoutineCast>(dbFastPutConvertRoutine[DBF_STRING][rec->ftvl]);
for (size_t i=0; i<ret.vs.size() && i<rec->mevl; i++) {
auto values = ret.get_string_array();
for (size_t i=0; i<values.size() && i<rec->mevl; i++) {
char* val = reinterpret_cast<char*>(rec->val) + i*dbValueSize(rec->ftvl);
status = convert(ret.vs[i].c_str(), val, 0);
if (status != 0) {
break;
if (convert(values[i].c_str(), val, 0) != 0) {
throw Variant::ConvertError();
}
rec->nevl++;
}
} else {
throw std::exception();
}
ctx->processCbStatus = 0;

} catch (std::exception& e) {
if (rec->tpro == 1) {
printf("[%s] %s\n", rec->name, e.what());
}
recGblSetSevr(rec, epicsAlarmCalc, epicsSevInvalid);
ctx->processCbStatus = -1;
}

rec->ctx->processCbStatus = (status == 0 ? 0 : -1);
callbackRequestProcessCallback(&rec->ctx->callback, rec->prio, rec);
callbackRequestProcessCallback(&ctx->callback, rec->prio, rec);
}

static long processRecord(dbCommon *common)
Expand Down
63 changes: 32 additions & 31 deletions src/pydev_ai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ struct PyDevContext {
CALLBACK callback;
IOSCANPVT scan;
int processCbStatus;
std::string code;
PyWrapper::ByteCode bytecode;
};

static std::map<std::string, IOSCANPVT> ioScanPvts;
Expand Down Expand Up @@ -84,45 +86,44 @@ static void processRecordCb(aiRecord* rec)
rec->val -= rec->aoff;
if (rec->aslo != 0.0) rec->val /= rec->aslo;

auto fields = Util::getFields(rec->inp.value.instio.string);
for (auto& keyval: fields) {
if (keyval.first == "VAL") keyval.second = std::to_string(rec->val);
else if (keyval.first == "RVAL") keyval.second = std::to_string(rec->rval);
else if (keyval.first == "ORAW") keyval.second = std::to_string(rec->oraw);
else if (keyval.first == "NAME") keyval.second = rec->name;
else if (keyval.first == "EGU") keyval.second = rec->egu;
else if (keyval.first == "HOPR") keyval.second = std::to_string(rec->hopr);
else if (keyval.first == "LOPR") keyval.second = std::to_string(rec->lopr);
else if (keyval.first == "PREC") keyval.second = std::to_string(rec->prec);
else if (keyval.first == "TPRO") keyval.second = std::to_string(rec->tpro);
std::string code = rec->inp.value.instio.string;
std::map<std::string, Variant> args;
for (auto& macro: Util::getMacros(code)) {
if (macro == "VAL") { args["pydevVAL"] = Variant(rec->val); code = Util::replaceMacro(code, "VAL", "pydevVAL"); }
else if (macro == "RVAL") { args["pydevRVAL"] = Variant(rec->rval); code = Util::replaceMacro(code, "RVAL", "pydevRVAL"); }
else if (macro == "ORAW") { args["pydevORAW"] = Variant(rec->oraw); code = Util::replaceMacro(code, "ORAW", "pydevORAW"); }
else if (macro == "NAME") { args["pydevNAME"] = Variant(rec->name); code = Util::replaceMacro(code, "NAME", "pydevNAME"); }
else if (macro == "EGU") { args["pydevEGU"] = Variant(rec->egu); code = Util::replaceMacro(code, "EGU", "pydevEGU"); }
else if (macro == "HOPR") { args["pydevHOPR"] = Variant(rec->hopr); code = Util::replaceMacro(code, "HOPR", "pydevHOPR"); }
else if (macro == "LOPR") { args["pydevLOPR"] = Variant(rec->lopr); code = Util::replaceMacro(code, "LOPR", "pydevLOPR"); }
else if (macro == "PREC") { args["pydevPREC"] = Variant(rec->prec); code = Util::replaceMacro(code, "PREC", "pydevPREC"); }
else if (macro == "TPRO") { args["pydevTPRO"] = Variant(rec->tpro); code = Util::replaceMacro(code, "TPRO", "pydevTPRO"); }
}
std::string code = Util::replaceFields(rec->inp.value.instio.string, fields);

try {
epicsFloat64 val;
if (PyWrapper::exec(code, (rec->tpro == 1), &val) == true) {
val = (val * rec->aslo) + rec->aoff;
if (rec->smoo == 0.0 || rec->udf)
rec->val = val;
else
rec->val = (rec->val * rec->smoo) + (val * (1.0 - rec->smoo));
rec->udf = 0;
ctx->processCbStatus = 0;
} else {
if (rec->tpro == 1) {
printf("ERROR: Can't convert returned Python type to double type\n");
}
recGblSetSevr(rec, epicsAlarmCalc, epicsSevInvalid);
ctx->processCbStatus = -1;
if (ctx->code != code) {
PyWrapper::destroy(std::move(ctx->bytecode));
ctx->bytecode = PyWrapper::compile(code, (rec->tpro == 1));
ctx->code = code;
}
double val = PyWrapper::eval(ctx->bytecode, args, (rec->tpro == 1)).get_double();

val = (val * rec->aslo) + rec->aoff;
if (rec->smoo == 0.0 || rec->udf)
rec->val = val;
else
rec->val = (rec->val * rec->smoo) + (val * (1.0 - rec->smoo));
rec->udf = 0;
ctx->processCbStatus = 2; // Conversion already done

} catch (std::exception& e) {
if (rec->tpro == 1) {
printf("[%s] %s\n", rec->name, e.what());
}
} catch (...) {
recGblSetSevr(rec, epicsAlarmCalc, epicsSevInvalid);
ctx->processCbStatus = -1;
}

if (ctx->processCbStatus == 0)
ctx->processCbStatus = 2; // Conversion already done

callbackRequestProcessCallback(&ctx->callback, rec->prio, rec);
}

Expand Down
Loading

0 comments on commit a3488dd

Please sign in to comment.