tracing/filter: Call filter predicate functions directly via a switch statement

Due to retpolines, indirect calls are much more expensive than direct
calls. The filters have a select set of functions it uses for the
predicates. Instead of using function pointers to call them, create a
filter_pred_fn_call() function that uses a switch statement to call the
predicate functions directly. This gives almost a 10% speedup to the
filter logic.

Using the histogram benchmark:

Before:

 # event histogram
 #
 # trigger info: hist:keys=delta:vals=hitcount:sort=delta:size=2048 if delta > 0 [active]
 #

{ delta:        113 } hitcount:        272
{ delta:        114 } hitcount:        840
{ delta:        118 } hitcount:        344
{ delta:        119 } hitcount:      25428
{ delta:        120 } hitcount:     350590
{ delta:        121 } hitcount:    1892484
{ delta:        122 } hitcount:    6205004
{ delta:        123 } hitcount:   11583521
{ delta:        124 } hitcount:   37590979
{ delta:        125 } hitcount:  108308504
{ delta:        126 } hitcount:  131672461
{ delta:        127 } hitcount:   88700598
{ delta:        128 } hitcount:   65939870
{ delta:        129 } hitcount:   45055004
{ delta:        130 } hitcount:   33174464
{ delta:        131 } hitcount:   31813493
{ delta:        132 } hitcount:   29011676
{ delta:        133 } hitcount:   22798782
{ delta:        134 } hitcount:   22072486
{ delta:        135 } hitcount:   17034113
{ delta:        136 } hitcount:    8982490
{ delta:        137 } hitcount:    2865908
{ delta:        138 } hitcount:     980382
{ delta:        139 } hitcount:    1651944
{ delta:        140 } hitcount:    4112073
{ delta:        141 } hitcount:    3963269
{ delta:        142 } hitcount:    1712508
{ delta:        143 } hitcount:     575941

After:

 # event histogram
 #
 # trigger info: hist:keys=delta:vals=hitcount:sort=delta:size=2048 if delta > 0 [active]
 #

{ delta:        103 } hitcount:         60
{ delta:        104 } hitcount:      16966
{ delta:        105 } hitcount:     396625
{ delta:        106 } hitcount:    3223400
{ delta:        107 } hitcount:   12053754
{ delta:        108 } hitcount:   20241711
{ delta:        109 } hitcount:   14850200
{ delta:        110 } hitcount:    4946599
{ delta:        111 } hitcount:    3479315
{ delta:        112 } hitcount:   18698299
{ delta:        113 } hitcount:   62388733
{ delta:        114 } hitcount:   95803834
{ delta:        115 } hitcount:   58278130
{ delta:        116 } hitcount:   15364800
{ delta:        117 } hitcount:    5586866
{ delta:        118 } hitcount:    2346880
{ delta:        119 } hitcount:    1131091
{ delta:        120 } hitcount:     620896
{ delta:        121 } hitcount:     236652
{ delta:        122 } hitcount:     105957
{ delta:        123 } hitcount:     119107
{ delta:        124 } hitcount:      54494
{ delta:        125 } hitcount:      63856
{ delta:        126 } hitcount:      64454
{ delta:        127 } hitcount:      34818
{ delta:        128 } hitcount:      41446
{ delta:        129 } hitcount:      51242
{ delta:        130 } hitcount:      28361
{ delta:        131 } hitcount:      23926

The peak before was 126ns per event, after the peak is 114ns, and the
fastest time went from 113ns to 103ns.

Link: https://lkml.kernel.org/r/20220906225529.781407172@goodmis.org

Cc: Ingo Molnar <mingo@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Tom Zanussi <zanussi@kernel.org>
Signed-off-by: default avatarSteven Rostedt (Google) <rostedt@goodmis.org>
parent 26c4e3d1
...@@ -43,10 +43,33 @@ enum filter_op_ids { OPS }; ...@@ -43,10 +43,33 @@ enum filter_op_ids { OPS };
static const char * ops[] = { OPS }; static const char * ops[] = { OPS };
typedef int (*filter_pred_fn_t) (struct filter_pred *pred, void *event); enum filter_pred_fn {
FILTER_PRED_FN_NOP,
FILTER_PRED_FN_64,
FILTER_PRED_FN_S64,
FILTER_PRED_FN_U64,
FILTER_PRED_FN_32,
FILTER_PRED_FN_S32,
FILTER_PRED_FN_U32,
FILTER_PRED_FN_16,
FILTER_PRED_FN_S16,
FILTER_PRED_FN_U16,
FILTER_PRED_FN_8,
FILTER_PRED_FN_S8,
FILTER_PRED_FN_U8,
FILTER_PRED_FN_COMM,
FILTER_PRED_FN_STRING,
FILTER_PRED_FN_STRLOC,
FILTER_PRED_FN_STRRELLOC,
FILTER_PRED_FN_PCHAR_USER,
FILTER_PRED_FN_PCHAR,
FILTER_PRED_FN_CPU,
FILTER_PRED_FN_,
FILTER_PRED_TEST_VISITED,
};
struct filter_pred { struct filter_pred {
filter_pred_fn_t fn; enum filter_pred_fn fn_num;
u64 val; u64 val;
struct regex regex; struct regex regex;
unsigned short *ops; unsigned short *ops;
...@@ -603,44 +626,48 @@ predicate_parse(const char *str, int nr_parens, int nr_preds, ...@@ -603,44 +626,48 @@ predicate_parse(const char *str, int nr_parens, int nr_preds,
return ERR_PTR(ret); return ERR_PTR(ret);
} }
enum pred_cmp_types {
PRED_CMP_TYPE_NOP,
PRED_CMP_TYPE_LT,
PRED_CMP_TYPE_LE,
PRED_CMP_TYPE_GT,
PRED_CMP_TYPE_GE,
PRED_CMP_TYPE_BAND,
};
#define DEFINE_COMPARISON_PRED(type) \ #define DEFINE_COMPARISON_PRED(type) \
static int filter_pred_LT_##type(struct filter_pred *pred, void *event) \ static int filter_pred_##type(struct filter_pred *pred, void *event) \
{ \ { \
switch (pred->op) { \
case OP_LT: { \
type *addr = (type *)(event + pred->offset); \ type *addr = (type *)(event + pred->offset); \
type val = (type)pred->val; \ type val = (type)pred->val; \
return *addr < val; \ return *addr < val; \
} \ } \
static int filter_pred_LE_##type(struct filter_pred *pred, void *event) \ case OP_LE: { \
{ \
type *addr = (type *)(event + pred->offset); \ type *addr = (type *)(event + pred->offset); \
type val = (type)pred->val; \ type val = (type)pred->val; \
return *addr <= val; \ return *addr <= val; \
} \ } \
static int filter_pred_GT_##type(struct filter_pred *pred, void *event) \ case OP_GT: { \
{ \
type *addr = (type *)(event + pred->offset); \ type *addr = (type *)(event + pred->offset); \
type val = (type)pred->val; \ type val = (type)pred->val; \
return *addr > val; \ return *addr > val; \
} \ } \
static int filter_pred_GE_##type(struct filter_pred *pred, void *event) \ case OP_GE: { \
{ \
type *addr = (type *)(event + pred->offset); \ type *addr = (type *)(event + pred->offset); \
type val = (type)pred->val; \ type val = (type)pred->val; \
return *addr >= val; \ return *addr >= val; \
} \ } \
static int filter_pred_BAND_##type(struct filter_pred *pred, void *event) \ case OP_BAND: { \
{ \
type *addr = (type *)(event + pred->offset); \ type *addr = (type *)(event + pred->offset); \
type val = (type)pred->val; \ type val = (type)pred->val; \
return !!(*addr & val); \ return !!(*addr & val); \
} \ } \
static const filter_pred_fn_t pred_funcs_##type[] = { \ default: \
filter_pred_LE_##type, \ return 0; \
filter_pred_LT_##type, \ } \
filter_pred_GE_##type, \ }
filter_pred_GT_##type, \
filter_pred_BAND_##type, \
};
#define DEFINE_EQUALITY_PRED(size) \ #define DEFINE_EQUALITY_PRED(size) \
static int filter_pred_##size(struct filter_pred *pred, void *event) \ static int filter_pred_##size(struct filter_pred *pred, void *event) \
...@@ -849,11 +876,6 @@ static int filter_pred_comm(struct filter_pred *pred, void *event) ...@@ -849,11 +876,6 @@ static int filter_pred_comm(struct filter_pred *pred, void *event)
return cmp ^ pred->not; return cmp ^ pred->not;
} }
static int filter_pred_none(struct filter_pred *pred, void *event)
{
return 0;
}
/* /*
* regex_match_foo - Basic regex callbacks * regex_match_foo - Basic regex callbacks
* *
...@@ -999,6 +1021,19 @@ static void filter_build_regex(struct filter_pred *pred) ...@@ -999,6 +1021,19 @@ static void filter_build_regex(struct filter_pred *pred)
} }
} }
#ifdef CONFIG_FTRACE_STARTUP_TEST
static int test_pred_visited_fn(struct filter_pred *pred, void *event);
#else
static int test_pred_visited_fn(struct filter_pred *pred, void *event)
{
return 0;
}
#endif
static int filter_pred_fn_call(struct filter_pred *pred, void *event);
/* return 1 if event matches, 0 otherwise (discard) */ /* return 1 if event matches, 0 otherwise (discard) */
int filter_match_preds(struct event_filter *filter, void *rec) int filter_match_preds(struct event_filter *filter, void *rec)
{ {
...@@ -1016,7 +1051,7 @@ int filter_match_preds(struct event_filter *filter, void *rec) ...@@ -1016,7 +1051,7 @@ int filter_match_preds(struct event_filter *filter, void *rec)
for (i = 0; prog[i].pred; i++) { for (i = 0; prog[i].pred; i++) {
struct filter_pred *pred = prog[i].pred; struct filter_pred *pred = prog[i].pred;
int match = pred->fn(pred, rec); int match = filter_pred_fn_call(pred, rec);
if (match == prog[i].when_to_branch) if (match == prog[i].when_to_branch)
i = prog[i].target; i = prog[i].target;
} }
...@@ -1202,10 +1237,10 @@ int filter_assign_type(const char *type) ...@@ -1202,10 +1237,10 @@ int filter_assign_type(const char *type)
return FILTER_OTHER; return FILTER_OTHER;
} }
static filter_pred_fn_t select_comparison_fn(enum filter_op_ids op, static enum filter_pred_fn select_comparison_fn(enum filter_op_ids op,
int field_size, int field_is_signed) int field_size, int field_is_signed)
{ {
filter_pred_fn_t fn = NULL; enum filter_pred_fn fn = FILTER_PRED_FN_NOP;
int pred_func_index = -1; int pred_func_index = -1;
switch (op) { switch (op) {
...@@ -1214,50 +1249,99 @@ static filter_pred_fn_t select_comparison_fn(enum filter_op_ids op, ...@@ -1214,50 +1249,99 @@ static filter_pred_fn_t select_comparison_fn(enum filter_op_ids op,
break; break;
default: default:
if (WARN_ON_ONCE(op < PRED_FUNC_START)) if (WARN_ON_ONCE(op < PRED_FUNC_START))
return NULL; return fn;
pred_func_index = op - PRED_FUNC_START; pred_func_index = op - PRED_FUNC_START;
if (WARN_ON_ONCE(pred_func_index > PRED_FUNC_MAX)) if (WARN_ON_ONCE(pred_func_index > PRED_FUNC_MAX))
return NULL; return fn;
} }
switch (field_size) { switch (field_size) {
case 8: case 8:
if (pred_func_index < 0) if (pred_func_index < 0)
fn = filter_pred_64; fn = FILTER_PRED_FN_64;
else if (field_is_signed) else if (field_is_signed)
fn = pred_funcs_s64[pred_func_index]; fn = FILTER_PRED_FN_S64;
else else
fn = pred_funcs_u64[pred_func_index]; fn = FILTER_PRED_FN_U64;
break; break;
case 4: case 4:
if (pred_func_index < 0) if (pred_func_index < 0)
fn = filter_pred_32; fn = FILTER_PRED_FN_32;
else if (field_is_signed) else if (field_is_signed)
fn = pred_funcs_s32[pred_func_index]; fn = FILTER_PRED_FN_S32;
else else
fn = pred_funcs_u32[pred_func_index]; fn = FILTER_PRED_FN_U32;
break; break;
case 2: case 2:
if (pred_func_index < 0) if (pred_func_index < 0)
fn = filter_pred_16; fn = FILTER_PRED_FN_16;
else if (field_is_signed) else if (field_is_signed)
fn = pred_funcs_s16[pred_func_index]; fn = FILTER_PRED_FN_S16;
else else
fn = pred_funcs_u16[pred_func_index]; fn = FILTER_PRED_FN_U16;
break; break;
case 1: case 1:
if (pred_func_index < 0) if (pred_func_index < 0)
fn = filter_pred_8; fn = FILTER_PRED_FN_8;
else if (field_is_signed) else if (field_is_signed)
fn = pred_funcs_s8[pred_func_index]; fn = FILTER_PRED_FN_S8;
else else
fn = pred_funcs_u8[pred_func_index]; fn = FILTER_PRED_FN_U8;
break; break;
} }
return fn; return fn;
} }
static int filter_pred_fn_call(struct filter_pred *pred, void *event)
{
switch (pred->fn_num) {
case FILTER_PRED_FN_64:
return filter_pred_64(pred, event);
case FILTER_PRED_FN_S64:
return filter_pred_s64(pred, event);
case FILTER_PRED_FN_U64:
return filter_pred_u64(pred, event);
case FILTER_PRED_FN_32:
return filter_pred_32(pred, event);
case FILTER_PRED_FN_S32:
return filter_pred_s32(pred, event);
case FILTER_PRED_FN_U32:
return filter_pred_u32(pred, event);
case FILTER_PRED_FN_16:
return filter_pred_16(pred, event);
case FILTER_PRED_FN_S16:
return filter_pred_s16(pred, event);
case FILTER_PRED_FN_U16:
return filter_pred_u16(pred, event);
case FILTER_PRED_FN_8:
return filter_pred_8(pred, event);
case FILTER_PRED_FN_S8:
return filter_pred_s8(pred, event);
case FILTER_PRED_FN_U8:
return filter_pred_u8(pred, event);
case FILTER_PRED_FN_COMM:
return filter_pred_comm(pred, event);
case FILTER_PRED_FN_STRING:
return filter_pred_string(pred, event);
case FILTER_PRED_FN_STRLOC:
return filter_pred_strloc(pred, event);
case FILTER_PRED_FN_STRRELLOC:
return filter_pred_strrelloc(pred, event);
case FILTER_PRED_FN_PCHAR_USER:
return filter_pred_pchar_user(pred, event);
case FILTER_PRED_FN_PCHAR:
return filter_pred_pchar(pred, event);
case FILTER_PRED_FN_CPU:
return filter_pred_cpu(pred, event);
case FILTER_PRED_TEST_VISITED:
return test_pred_visited_fn(pred, event);
default:
return 0;
}
}
/* Called when a predicate is encountered by predicate_parse() */ /* Called when a predicate is encountered by predicate_parse() */
static int parse_pred(const char *str, void *data, static int parse_pred(const char *str, void *data,
int pos, struct filter_parse_error *pe, int pos, struct filter_parse_error *pe,
...@@ -1351,7 +1435,7 @@ static int parse_pred(const char *str, void *data, ...@@ -1351,7 +1435,7 @@ static int parse_pred(const char *str, void *data,
parse_error(pe, FILT_ERR_IP_FIELD_ONLY, pos + i); parse_error(pe, FILT_ERR_IP_FIELD_ONLY, pos + i);
goto err_free; goto err_free;
} }
pred->fn = filter_pred_none; pred->fn_num = FILTER_PRED_FN_NOP;
/* /*
* Quotes are not required, but if they exist then we need * Quotes are not required, but if they exist then we need
...@@ -1429,16 +1513,16 @@ static int parse_pred(const char *str, void *data, ...@@ -1429,16 +1513,16 @@ static int parse_pred(const char *str, void *data,
filter_build_regex(pred); filter_build_regex(pred);
if (field->filter_type == FILTER_COMM) { if (field->filter_type == FILTER_COMM) {
pred->fn = filter_pred_comm; pred->fn_num = FILTER_PRED_FN_COMM;
} else if (field->filter_type == FILTER_STATIC_STRING) { } else if (field->filter_type == FILTER_STATIC_STRING) {
pred->fn = filter_pred_string; pred->fn_num = FILTER_PRED_FN_STRING;
pred->regex.field_len = field->size; pred->regex.field_len = field->size;
} else if (field->filter_type == FILTER_DYN_STRING) { } else if (field->filter_type == FILTER_DYN_STRING) {
pred->fn = filter_pred_strloc; pred->fn_num = FILTER_PRED_FN_STRLOC;
} else if (field->filter_type == FILTER_RDYN_STRING) } else if (field->filter_type == FILTER_RDYN_STRING)
pred->fn = filter_pred_strrelloc; pred->fn_num = FILTER_PRED_FN_STRRELLOC;
else { else {
if (!ustring_per_cpu) { if (!ustring_per_cpu) {
...@@ -1449,9 +1533,9 @@ static int parse_pred(const char *str, void *data, ...@@ -1449,9 +1533,9 @@ static int parse_pred(const char *str, void *data,
} }
if (ustring) if (ustring)
pred->fn = filter_pred_pchar_user; pred->fn_num = FILTER_PRED_FN_PCHAR_USER;
else else
pred->fn = filter_pred_pchar; pred->fn_num = FILTER_PRED_FN_PCHAR;
} }
/* go past the last quote */ /* go past the last quote */
i++; i++;
...@@ -1499,9 +1583,9 @@ static int parse_pred(const char *str, void *data, ...@@ -1499,9 +1583,9 @@ static int parse_pred(const char *str, void *data,
pred->val = val; pred->val = val;
if (field->filter_type == FILTER_CPU) if (field->filter_type == FILTER_CPU)
pred->fn = filter_pred_cpu; pred->fn_num = FILTER_PRED_FN_CPU;
else { else {
pred->fn = select_comparison_fn(pred->op, field->size, pred->fn_num = select_comparison_fn(pred->op, field->size,
field->is_signed); field->is_signed);
if (pred->op == OP_NE) if (pred->op == OP_NE)
pred->not = 1; pred->not = 1;
...@@ -2309,7 +2393,7 @@ static void update_pred_fn(struct event_filter *filter, char *fields) ...@@ -2309,7 +2393,7 @@ static void update_pred_fn(struct event_filter *filter, char *fields)
struct filter_pred *pred = prog[i].pred; struct filter_pred *pred = prog[i].pred;
struct ftrace_event_field *field = pred->field; struct ftrace_event_field *field = pred->field;
WARN_ON_ONCE(!pred->fn); WARN_ON_ONCE(pred->fn_num == FILTER_PRED_FN_NOP);
if (!field) { if (!field) {
WARN_ONCE(1, "all leafs should have field defined %d", i); WARN_ONCE(1, "all leafs should have field defined %d", i);
...@@ -2319,7 +2403,7 @@ static void update_pred_fn(struct event_filter *filter, char *fields) ...@@ -2319,7 +2403,7 @@ static void update_pred_fn(struct event_filter *filter, char *fields)
if (!strchr(fields, *field->name)) if (!strchr(fields, *field->name))
continue; continue;
pred->fn = test_pred_visited_fn; pred->fn_num = FILTER_PRED_TEST_VISITED;
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment