@@ -111,6 +111,7 @@ static long dxp[256];
111
111
#else
112
112
#define OPCACHE_MIN_RUNS 1024 /* create opcache when code executed this time */
113
113
#endif
114
+ #define OPCODE_CACHE_MAX_TRIES 20
114
115
#define OPCACHE_STATS 0 /* Enable stats */
115
116
116
117
#if OPCACHE_STATS
@@ -120,6 +121,12 @@ static size_t opcache_code_objects_extra_mem = 0;
120
121
static size_t opcache_global_opts = 0 ;
121
122
static size_t opcache_global_hits = 0 ;
122
123
static size_t opcache_global_misses = 0 ;
124
+
125
+ static size_t opcache_attr_opts = 0 ;
126
+ static size_t opcache_attr_hits = 0 ;
127
+ static size_t opcache_attr_misses = 0 ;
128
+ static size_t opcache_attr_deopts = 0 ;
129
+ static size_t opcache_attr_total = 0 ;
123
130
#endif
124
131
125
132
@@ -365,6 +372,25 @@ _PyEval_Fini(void)
365
372
opcache_global_opts );
366
373
367
374
fprintf (stderr , "\n" );
375
+
376
+ fprintf (stderr , "-- Opcode cache LOAD_ATTR hits = %zd (%d%%)\n" ,
377
+ opcache_attr_hits ,
378
+ (int ) (100.0 * opcache_attr_hits /
379
+ opcache_attr_total ));
380
+
381
+ fprintf (stderr , "-- Opcode cache LOAD_ATTR misses = %zd (%d%%)\n" ,
382
+ opcache_attr_misses ,
383
+ (int ) (100.0 * opcache_attr_misses /
384
+ opcache_attr_total ));
385
+
386
+ fprintf (stderr , "-- Opcode cache LOAD_ATTR opts = %zd\n" ,
387
+ opcache_attr_opts );
388
+
389
+ fprintf (stderr , "-- Opcode cache LOAD_ATTR deopts = %zd\n" ,
390
+ opcache_attr_deopts );
391
+
392
+ fprintf (stderr , "-- Opcode cache LOAD_ATTR total = %zd\n" ,
393
+ opcache_attr_total );
368
394
#endif
369
395
}
370
396
@@ -1224,16 +1250,43 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
1224
1250
do { \
1225
1251
co_opcache = NULL; \
1226
1252
if (co->co_opcache != NULL) { \
1227
- unsigned char co_opt_offset = \
1253
+ unsigned char co_opcache_offset = \
1228
1254
co->co_opcache_map[next_instr - first_instr]; \
1229
- if (co_opt_offset > 0) { \
1230
- assert(co_opt_offset <= co->co_opcache_size); \
1231
- co_opcache = &co->co_opcache[co_opt_offset - 1]; \
1255
+ if (co_opcache_offset > 0) { \
1256
+ assert(co_opcache_offset <= co->co_opcache_size); \
1257
+ co_opcache = &co->co_opcache[co_opcache_offset - 1]; \
1232
1258
assert(co_opcache != NULL); \
1233
1259
} \
1234
1260
} \
1235
1261
} while (0)
1236
1262
1263
+ #define OPCACHE_DEOPT () \
1264
+ do { \
1265
+ if (co_opcache != NULL) { \
1266
+ co_opcache->optimized = -1; \
1267
+ unsigned char co_opcache_offset = \
1268
+ co->co_opcache_map[next_instr - first_instr]; \
1269
+ assert(co_opcache_offset <= co->co_opcache_size); \
1270
+ co->co_opcache_map[co_opcache_offset] = 0; \
1271
+ co_opcache = NULL; \
1272
+ } \
1273
+ } while (0)
1274
+
1275
+ #define OPCACHE_DEOPT_LOAD_ATTR () \
1276
+ do { \
1277
+ if (co_opcache != NULL) { \
1278
+ OPCACHE_STAT_ATTR_DEOPT(); \
1279
+ OPCACHE_DEOPT(); \
1280
+ } \
1281
+ } while (0)
1282
+
1283
+ #define OPCACHE_MAYBE_DEOPT_LOAD_ATTR () \
1284
+ do { \
1285
+ if (co_opcache != NULL && --co_opcache->optimized <= 0) { \
1286
+ OPCACHE_DEOPT_LOAD_ATTR(); \
1287
+ } \
1288
+ } while (0)
1289
+
1237
1290
#if OPCACHE_STATS
1238
1291
1239
1292
#define OPCACHE_STAT_GLOBAL_HIT () \
@@ -1251,12 +1304,43 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
1251
1304
if (co->co_opcache != NULL) opcache_global_opts++; \
1252
1305
} while (0)
1253
1306
1307
+ #define OPCACHE_STAT_ATTR_HIT () \
1308
+ do { \
1309
+ if (co->co_opcache != NULL) opcache_attr_hits++; \
1310
+ } while (0)
1311
+
1312
+ #define OPCACHE_STAT_ATTR_MISS () \
1313
+ do { \
1314
+ if (co->co_opcache != NULL) opcache_attr_misses++; \
1315
+ } while (0)
1316
+
1317
+ #define OPCACHE_STAT_ATTR_OPT () \
1318
+ do { \
1319
+ if (co->co_opcache!= NULL) opcache_attr_opts++; \
1320
+ } while (0)
1321
+
1322
+ #define OPCACHE_STAT_ATTR_DEOPT () \
1323
+ do { \
1324
+ if (co->co_opcache != NULL) opcache_attr_deopts++; \
1325
+ } while (0)
1326
+
1327
+ #define OPCACHE_STAT_ATTR_TOTAL () \
1328
+ do { \
1329
+ if (co->co_opcache != NULL) opcache_attr_total++; \
1330
+ } while (0)
1331
+
1254
1332
#else /* OPCACHE_STATS */
1255
1333
1256
1334
#define OPCACHE_STAT_GLOBAL_HIT ()
1257
1335
#define OPCACHE_STAT_GLOBAL_MISS ()
1258
1336
#define OPCACHE_STAT_GLOBAL_OPT ()
1259
1337
1338
+ #define OPCACHE_STAT_ATTR_HIT ()
1339
+ #define OPCACHE_STAT_ATTR_MISS ()
1340
+ #define OPCACHE_STAT_ATTR_OPT ()
1341
+ #define OPCACHE_STAT_ATTR_DEOPT ()
1342
+ #define OPCACHE_STAT_ATTR_TOTAL ()
1343
+
1260
1344
#endif
1261
1345
1262
1346
/* Start of code */
@@ -3023,7 +3107,134 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
3023
3107
case TARGET (LOAD_ATTR ): {
3024
3108
PyObject * name = GETITEM (names , oparg );
3025
3109
PyObject * owner = TOP ();
3026
- PyObject * res = PyObject_GetAttr (owner , name );
3110
+
3111
+ PyTypeObject * type = Py_TYPE (owner );
3112
+ PyObject * res ;
3113
+ PyObject * * dictptr ;
3114
+ PyObject * dict ;
3115
+ _PyOpCodeOpt_LoadAttr * la ;
3116
+
3117
+ OPCACHE_STAT_ATTR_TOTAL ();
3118
+
3119
+ OPCACHE_CHECK ();
3120
+ if (co_opcache != NULL && PyType_HasFeature (type , Py_TPFLAGS_VALID_VERSION_TAG ))
3121
+ {
3122
+ if (co_opcache -> optimized > 0 ) {
3123
+ /* Fast path -- cache hit makes LOAD_ATTR ~30% faster */
3124
+ la = & co_opcache -> u .la ;
3125
+ if (la -> type == type && la -> tp_version_tag == type -> tp_version_tag )
3126
+ {
3127
+ assert (type -> tp_dict != NULL );
3128
+ assert (type -> tp_dictoffset > 0 );
3129
+
3130
+ dictptr = (PyObject * * ) ((char * )owner + type -> tp_dictoffset );
3131
+ dict = * dictptr ;
3132
+ if (dict != NULL && PyDict_CheckExact (dict )) {
3133
+ Py_ssize_t hint = la -> hint ;
3134
+ Py_INCREF (dict );
3135
+ res = NULL ;
3136
+ la -> hint = _PyDict_GetItemHint ((PyDictObject * )dict , name , hint , & res );
3137
+
3138
+ if (res != NULL ) {
3139
+ if (la -> hint == hint && hint >= 0 ) {
3140
+ /* Our hint has helped -- cache hit. */
3141
+ OPCACHE_STAT_ATTR_HIT ();
3142
+ } else {
3143
+ /* The hint we provided didn't work.
3144
+ Maybe next time? */
3145
+ OPCACHE_MAYBE_DEOPT_LOAD_ATTR ();
3146
+ }
3147
+
3148
+ Py_INCREF (res );
3149
+ SET_TOP (res );
3150
+ Py_DECREF (owner );
3151
+ Py_DECREF (dict );
3152
+ DISPATCH ();
3153
+ } else {
3154
+ // This attribute can be missing sometimes -- we
3155
+ // don't want to optimize this lookup.
3156
+ OPCACHE_DEOPT_LOAD_ATTR ();
3157
+ Py_DECREF (dict );
3158
+ }
3159
+ } else {
3160
+ // There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact
3161
+ OPCACHE_DEOPT_LOAD_ATTR ();
3162
+ }
3163
+ } else {
3164
+ // The type of the object has either been updated,
3165
+ // or is different. Maybe it will stabilize?
3166
+ OPCACHE_MAYBE_DEOPT_LOAD_ATTR ();
3167
+ }
3168
+
3169
+ OPCACHE_STAT_ATTR_MISS ();
3170
+ }
3171
+
3172
+ if (co_opcache != NULL && /* co_opcache can be NULL after a DEOPT() call. */
3173
+ type -> tp_getattro == PyObject_GenericGetAttr )
3174
+ {
3175
+ PyObject * descr ;
3176
+ Py_ssize_t ret ;
3177
+
3178
+ if (type -> tp_dictoffset > 0 ) {
3179
+ if (type -> tp_dict == NULL ) {
3180
+ if (PyType_Ready (type ) < 0 ) {
3181
+ Py_DECREF (owner );
3182
+ SET_TOP (NULL );
3183
+ goto error ;
3184
+ }
3185
+ }
3186
+
3187
+ descr = _PyType_Lookup (type , name );
3188
+ if (descr == NULL ||
3189
+ descr -> ob_type -> tp_descr_get == NULL ||
3190
+ !PyDescr_IsData (descr ))
3191
+ {
3192
+ dictptr = (PyObject * * ) ((char * )owner + type -> tp_dictoffset );
3193
+ dict = * dictptr ;
3194
+
3195
+ if (dict != NULL && PyDict_CheckExact (dict )) {
3196
+ Py_INCREF (dict );
3197
+ res = NULL ;
3198
+ ret = _PyDict_GetItemHint ((PyDictObject * )dict , name , -1 , & res );
3199
+ if (res != NULL ) {
3200
+ Py_INCREF (res );
3201
+ Py_DECREF (dict );
3202
+ Py_DECREF (owner );
3203
+ SET_TOP (res );
3204
+
3205
+ if (co_opcache -> optimized == 0 ) {
3206
+ // First time we optimize this opcode. */
3207
+ OPCACHE_STAT_ATTR_OPT ();
3208
+ co_opcache -> optimized = OPCODE_CACHE_MAX_TRIES ;
3209
+ }
3210
+
3211
+ la = & co_opcache -> u .la ;
3212
+ la -> type = type ;
3213
+ la -> tp_version_tag = type -> tp_version_tag ;
3214
+ la -> hint = ret ;
3215
+
3216
+ DISPATCH ();
3217
+ }
3218
+ Py_DECREF (dict );
3219
+ } else {
3220
+ // There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact
3221
+ OPCACHE_DEOPT_LOAD_ATTR ();
3222
+ }
3223
+ } else {
3224
+ // We failed to find an attribute without a data-like descriptor
3225
+ OPCACHE_DEOPT_LOAD_ATTR ();
3226
+ }
3227
+ } else {
3228
+ // The object's class does not have a tp_dictoffset we can use
3229
+ OPCACHE_DEOPT_LOAD_ATTR ();
3230
+ }
3231
+ } else if (type -> tp_getattro != PyObject_GenericGetAttr ) {
3232
+ OPCACHE_DEOPT_LOAD_ATTR ();
3233
+ }
3234
+ }
3235
+
3236
+ /* slow path */
3237
+ res = PyObject_GetAttr (owner , name );
3027
3238
Py_DECREF (owner );
3028
3239
SET_TOP (res );
3029
3240
if (res == NULL )
0 commit comments