NEURON
perf_visitor.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2023 Blue Brain Project, EPFL.
3  * See the top-level LICENSE file for details.
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
9 
10 #include <cassert>
11 #include <utility>
12 
13 #include "ast/all.hpp"
14 #include "printer/json_printer.hpp"
15 
16 
17 namespace nmodl {
18 namespace visitor {
19 
20 using printer::JSONPrinter;
21 using symtab::Symbol;
24 using utils::PerfStat;
25 
26 PerfVisitor::PerfVisitor(const std::string& filename)
27  : printer(new JSONPrinter(filename)) {}
28 
29 void PerfVisitor::compact_json(bool flag) {
30  printer->compact_json(flag);
31 }
32 
33 
34 /// count math operations from all binary expressions
36  bool assign_op = false;
37 
38  if (start_measurement) {
39  auto value = node.get_op().get_value();
40  switch (value) {
41  case ast::BOP_ADDITION:
43  break;
44 
47  break;
48 
51  break;
52 
53  case ast::BOP_DIVISION:
55  break;
56 
57  case ast::BOP_POWER:
59  break;
60 
61  case ast::BOP_AND:
63  break;
64 
65  case ast::BOP_OR:
67  break;
68 
69  case ast::BOP_GREATER:
71  break;
72 
75  break;
76 
77  case ast::BOP_LESS:
79  break;
80 
83  break;
84 
85  case ast::BOP_ASSIGN:
87  assign_op = true;
88  break;
89 
90  case ast::BOP_NOT_EQUAL:
92  break;
93 
96  break;
97 
98  default:
99  throw std::logic_error("Binary operator not handled in perf visitor");
100  }
101  }
102 
103  /// if visiting assignment expression, symbols from lhs
104  /// are written and hence need flag to track
105  if (assign_op) {
107  }
108 
109  node.get_lhs()->accept(*this);
110 
111  /// lhs is done (rhs is read only)
112  visiting_lhs_expression = false;
113 
114  node.get_rhs()->accept(*this);
115 }
116 
117 /// add performance stats to json printer
119  const auto& keys = nmodl::utils::PerfStat::keys();
120  const auto& values = perf.values();
121  assert(keys.size() == values.size());
122 
123  for (size_t i = 0; i < keys.size(); i++) {
124  printer->add_node(values[i], keys[i]);
125  }
126 }
127 
128 /** Helper function used by all ast nodes : visit all children
129  * recursively and performance stats get added on stack. Once
130  * all children visited, we get total performance by summing
131  * perfstat of all children.
132  */
134  start_measurement = true;
135 
136  node.visit_children(*this);
137 
138  PerfStat perf;
139  while (!children_blocks_perf.empty()) {
140  perf = perf + children_blocks_perf.top();
141  children_blocks_perf.pop();
142  }
143 
144  auto symtab = node.get_symbol_table();
145  if (symtab == nullptr) {
146  throw std::runtime_error("Perfvisitor : symbol table not setup for " +
147  node.get_node_type_name());
148  }
149 
150  auto name = symtab->name();
151  if (node.is_derivative_block()) {
152  name = node.get_node_type_name();
153  }
154 
155  if (printer) {
156  printer->push_block(name);
157  }
158 
159  perf.title = "Performance Statistics of " + name;
160  perf.print(stream);
161 
162  if (printer) {
163  add_perf_to_printer(perf);
164  printer->pop_block();
165  }
166 
167  start_measurement = false;
168 
169  /// clear var usage map
170  for (auto& var_set: var_usage) {
171  var_set.second.clear();
172  }
173 }
174 
175 /// count function calls and "most useful" or "commonly used" math functions
177  under_function_call = true;
178 
179  if (start_measurement) {
180  auto name = node.get_node_name();
181  if (name == "exp") {
183  } else if (name == "log") {
185  } else if (name == "pow") {
187  }
188  node.visit_children(*this);
189 
190  auto symbol = current_symtab->lookup_in_scope(name);
191  auto method_property = NmodlType::procedure_block | NmodlType::function_block;
192  if (symbol != nullptr && symbol->has_any_property(method_property)) {
194  } else {
196  }
197  }
198 
199  under_function_call = false;
200 }
201 
202 /// every variable used is of type name, update counters
204  update_memory_ops(node.get_node_name());
205  node.visit_children(*this);
206 }
207 
208 /// prime name derived from identifier and hence need to be handled here
210  update_memory_ops(node.get_node_name());
211  node.visit_children(*this);
212 }
213 
215  if (start_measurement) {
217  node.visit_children(*this);
218  }
219 }
220 
222  if (start_measurement) {
224  node.visit_children(*this);
225  }
226 }
227 
229  /// number of instance variables: range or assigned variables
230  /// one caveat is that the global variables appearing in
231  /// assigned block are not treated as range
233 
234  NmodlType property = NmodlType::range_var | NmodlType::assigned_definition |
235  NmodlType::state_var;
236  auto variables = current_symtab->get_variables_with_properties(property);
237 
238  for (auto& variable: variables) {
239  if (!variable->has_any_property(NmodlType::global_var)) {
241  if (variable->has_any_property(NmodlType::param_assign)) {
243  }
244  if (variable->has_any_status(Status::localized)) {
246  }
247  }
248  }
249 
250  /// state variables have state_var property
251  property = NmodlType::state_var;
252  variables = current_symtab->get_variables_with_properties(property);
253  num_state_variables = static_cast<int>(variables.size());
254 
255  /// pointer variables have pointer/bbcorepointer
256  property = NmodlType::pointer_var | NmodlType::bbcore_pointer_var;
257  variables = current_symtab->get_variables_with_properties(property);
258  num_pointer_variables = static_cast<int>(variables.size());
259 
260  /// RANDOM variables have NmodlType::random_var
261  property = NmodlType::random_var;
262  variables = current_symtab->get_variables_with_properties(property);
263  num_random_variables = static_cast<int>(variables.size());
264 
265 
266  /// number of global variables : parameters and pointers could appear also
267  /// as range variables and hence need to filter out. But if anything declared
268  /// as global is always global.
269  property = NmodlType::global_var | NmodlType::param_assign | NmodlType::bbcore_pointer_var |
270  NmodlType::pointer_var;
271  variables = current_symtab->get_variables_with_properties(property);
273  for (auto& variable: variables) {
274  auto is_global = variable->has_any_property(NmodlType::global_var);
275  property = NmodlType::range_var | NmodlType::assigned_definition;
276  if (!variable->has_any_property(property) || is_global) {
278  if (variable->has_any_property(NmodlType::param_assign)) {
280  }
281  if (variable->has_any_status(Status::localized)) {
283  }
284  }
285  }
286 }
287 
289  stream << std::endl;
290 
291  stream << "#VARIABLES :: ";
292  stream << " INSTANCE : " << num_instance_variables << " ";
293  stream << "[ CONSTANT " << num_constant_instance_variables << ", ";
294  stream << "LOCALIZED " << num_localized_instance_variables << " ]";
295 
296  stream << " GLOBAL VARIABLES : " << num_global_variables << " ";
297  stream << "[ CONSTANT " << num_constant_global_variables << ", ";
298  stream << "LOCALIZED " << num_localized_global_variables << " ]";
299 
300  stream << " STATE : " << num_state_variables;
301  stream << " POINTER : " << num_pointer_variables << std::endl;
302  stream << " RANDOM : " << num_random_variables << std::endl;
303 
304  if (printer) {
305  printer->push_block("MemoryInfo");
306 
307  printer->push_block("Instance");
308  printer->add_node(std::to_string(num_instance_variables), "total");
311  printer->pop_block();
312 
313  printer->push_block("Global");
314  printer->add_node(std::to_string(num_global_variables), "total");
315  printer->add_node(std::to_string(num_global_variables), "const");
316  printer->add_node(std::to_string(num_localized_global_variables), "localized");
317  printer->pop_block();
318 
319  printer->push_block("State");
320  printer->add_node(std::to_string(num_state_variables), "total");
321  printer->pop_block();
322 
323  printer->push_block("Pointer");
324  printer->add_node(std::to_string(num_pointer_variables), "total");
325  printer->pop_block();
326 
327  printer->push_block("RANDOM");
328  printer->add_node(std::to_string(num_random_variables), "total");
329  printer->pop_block();
330 
331  printer->pop_block();
332  }
333 }
334 
336  if (printer) {
337  printer->push_block("BlockPerf");
338  }
339 
340  node.visit_children(*this);
341  std::string title = "Total Performance Statistics";
344 
345  if (printer) {
346  printer->push_block("total");
348  printer->pop_block();
349  printer->pop_block();
350  }
351 
352  current_symtab = node.get_symbol_table();
353  count_variables();
355 }
356 
357 /// skip initial block under net_receive block
361  }
362 }
363 
366 }
367 
370 }
371 
374 }
375 
378 }
379 
382 }
383 
386 }
387 
390 }
391 
394 }
395 
398 }
399 
403  under_net_receive_block = false;
404 }
405 
408 }
409 
412 }
413 
416 }
417 
420 }
421 
424 }
425 
428 }
429 
430 /** Blocks like function can have multiple statement blocks and
431  * blocks like net receive has nested initial blocks. Hence need
432  * to maintain separate stack.
433  */
435  /// starting new block, store current state
437 
438  current_symtab = node.get_symbol_table();
439 
440  if (current_symtab == nullptr) {
441  throw std::runtime_error("Perfvisitor : symbol table not setup for " +
442  node.get_node_type_name());
443  }
444 
445  /// new block perf starts from zero
447 
448  node.visit_children(*this);
449 
450  /// add performance of all visited children
452 
454 
455  // go back to parent block's state
457  blocks_perf.pop();
458 }
459 
460 /// solve is not a statement but could have associated block
461 /// and hence could/should not be skipped completely
462 /// we can't ignore the block because it could have associated
463 /// statement block (in theory)
465  under_solve_block = true;
466  node.visit_children(*this);
467  under_solve_block = false;
468 }
469 
471  if (start_measurement) {
472  auto value = node.get_op().get_value();
473  switch (value) {
474  case ast::UOP_NEGATION:
476  break;
477 
478  case ast::UOP_NOT:
480  break;
481 
482  default:
483  throw std::logic_error("Unary operator not handled in perf visitor");
484  }
485  }
486  node.visit_children(*this);
487 }
488 
489 /** Certain statements / symbols needs extra check while measuring
490  * read/write operations. For example, for expression "exp(a+b)",
491  * "exp" is an external math function and we should not increment read
492  * count for "exp" symbol. Same for solve statement where name will
493  * be derivative block name and neuron solver method.
494  */
495 bool PerfVisitor::symbol_to_skip(const std::shared_ptr<Symbol>& symbol) const {
496  bool skip = false;
497 
498  auto is_method = symbol->has_any_property(NmodlType::extern_method | NmodlType::function_block);
500  skip = true;
501  }
502 
503  is_method = symbol->has_any_property(NmodlType::derivative_block | NmodlType::extern_method);
504  if (is_method && under_solve_block) {
505  skip = true;
506  }
507 
508  return skip;
509 }
510 
511 bool PerfVisitor::is_local_variable(const std::shared_ptr<Symbol>& symbol) {
512  bool is_local = false;
513  /// in the function when we write to function variable then consider it as local variable
514  auto properties = NmodlType::local_var | NmodlType::argument | NmodlType::function_block;
515  if (symbol->has_any_property(properties)) {
516  is_local = true;
517  }
518  return is_local;
519 }
520 
521 bool PerfVisitor::is_constant_variable(const std::shared_ptr<Symbol>& symbol) {
522  bool is_constant = false;
523  auto properties = NmodlType::param_assign;
524  if (symbol->has_any_property(properties)) {
525  is_constant = true;
526  }
527  return is_constant;
528 }
529 
530 
531 /** Find symbol in closest scope (up to parent) and update
532  * read/write count. Also update ops count in current block.
533  */
534 void PerfVisitor::update_memory_ops(const std::string& name) {
535  if (!start_measurement || current_symtab == nullptr) {
536  return;
537  }
538 
539  auto symbol = current_symtab->lookup_in_scope(name);
540  if (symbol == nullptr || symbol_to_skip(symbol)) {
541  return;
542  }
543 
544  if (is_local_variable(symbol)) {
546  symbol->write();
548  } else {
549  symbol->read();
551  }
552  return;
553  }
554 
555  /// lhs symbols get written
557  symbol->write();
558  if (is_constant_variable(symbol)) {
562  var_usage[const_memw_key].insert(name);
563  }
564  } else {
568  var_usage[global_memw_key].insert(name);
569  }
570  }
571  return;
572  }
573 
574  /// rhs symbols get read
575  symbol->read();
576  if (is_constant_variable(symbol)) {
580  var_usage[const_memr_key].insert(name);
581  }
582  } else {
586  var_usage[global_memr_key].insert(name);
587  }
588  }
589 }
590 
591 } // namespace visitor
592 } // namespace nmodl
Auto generated AST classes declaration.
Represents a AFTER block in NMODL.
Definition: after_block.hpp:51
Represents a block to be executed before or after another block.
Definition: ba_block.hpp:40
Represents a BEFORE block in NMODL.
Represents binary expression in the NMODL.
Represents a BREAKPOINT block in NMODL.
Represents a CONSTRUCTOR block in the NMODL.
Represents DERIVATIVE block in the NMODL.
Represents a DESTRUCTOR block in the NMODL.
Represents a INITIAL block in the NMODL.
Represents LINEAR block in the NMODL.
Represents a name.
Definition: name.hpp:44
Represents NONLINEAR block in the NMODL.
Represents a prime variable (for ODE)
Definition: prime_name.hpp:48
Represents top level AST node for whole NMODL input.
Definition: program.hpp:39
Represents block encapsulating list of statements.
Helper class for printing AST in JSON form.
std::vector< std::shared_ptr< Symbol > > get_variables_with_properties(syminfo::NmodlType properties, bool all=false) const
get variables with properties
std::shared_ptr< Symbol > lookup_in_scope(const std::string &name) const
check if symbol with given name exist in the current table (including all parents)
void visit_ba_block(const ast::BABlock &node) override
visit node of type ast::BABlock
void visit_program(const ast::Program &node) override
visit node of type ast::Program
static bool is_constant_variable(const std::shared_ptr< symtab::Symbol > &symbol)
void compact_json(bool flag)
void visit_initial_block(const ast::InitialBlock &node) override
skip initial block under net_receive block
int num_pointer_variables
count of pointer / bbcorepointer variables
void visit_constructor_block(const ast::ConstructorBlock &node) override
visit node of type ast::ConstructorBlock
int num_random_variables
count of RANDOM variables
int num_localized_global_variables
subset of global variables which are localized
symtab::SymbolTable * current_symtab
symbol table of current block being visited
void visit_breakpoint_block(const ast::BreakpointBlock &node) override
visit node of type ast::BreakpointBlock
void update_memory_ops(const std::string &name)
Find symbol in closest scope (up to parent) and update read/write count.
void visit_if_statement(const ast::IfStatement &node) override
visit node of type ast::IfStatement
void visit_name(const ast::Name &node) override
every variable used is of type name, update counters
bool visiting_lhs_expression
true while visiting lhs of binary expression (to count write operations)
utils::PerfStat total_perf
total performance of mod file
void measure_performance(const ast::Ast &node)
Helper function used by all ast nodes : visit all children recursively and performance stats get adde...
void visit_destructor_block(const ast::DestructorBlock &node) override
visit node of type ast::DestructorBlock
void visit_discrete_block(const ast::DiscreteBlock &node) override
visit node of type ast::DiscreteBlock
utils::PerfStat current_block_perf
performance of current block
int num_constant_instance_variables
subset of instance variables which are constant
void visit_derivative_block(const ast::DerivativeBlock &node) override
visit node of type ast::DerivativeBlock
int num_localized_instance_variables
subset of instance variables which are localized
std::string const_memr_key
keys used in map to track var usage
std::stack< utils::PerfStat > blocks_perf
performance stats of all blocks being visited in recursive chain
std::unique_ptr< printer::JSONPrinter > printer
to print to json file
void visit_unary_expression(const ast::UnaryExpression &node) override
visit node of type ast::UnaryExpression
void visit_prime_name(const ast::PrimeName &node) override
prime name derived from identifier and hence need to be handled here
void visit_function_table_block(const ast::FunctionTableBlock &node) override
visit node of type ast::FunctionTableBlock
std::map< std::string, std::set< std::string > > var_usage
map of variables to count unique read-writes
void add_perf_to_printer(const utils::PerfStat &perf) const
add performance stats to json printer
void visit_statement_block(const ast::StatementBlock &node) override
Blocks like function can have multiple statement blocks and blocks like net receive has nested initia...
void visit_before_block(const ast::BeforeBlock &node) override
visit node of type ast::BeforeBlock
void visit_function_call(const ast::FunctionCall &node) override
count function calls and "most useful" or "commonly used" math functions
void visit_solve_block(const ast::SolveBlock &node) override
solve is not a statement but could have associated block and hence could/should not be skipped comple...
void visit_else_if_statement(const ast::ElseIfStatement &node) override
visit node of type ast::ElseIfStatement
void visit_for_netcon(const ast::ForNetcon &node) override
visit node of type ast::ForNetcon
std::stringstream stream
if not json, all goes to string
void visit_binary_expression(const ast::BinaryExpression &node) override
count math operations from all binary expressions
std::stack< utils::PerfStat > children_blocks_perf
performance of current all childrens
bool under_function_call
whether function call is being visited
void visit_after_block(const ast::AfterBlock &node) override
visit node of type ast::AfterBlock
void visit_net_receive_block(const ast::NetReceiveBlock &node) override
visit node of type ast::NetReceiveBlock
int num_global_variables
count of global variables
void visit_kinetic_block(const ast::KineticBlock &node) override
visit node of type ast::KineticBlock
void visit_linear_block(const ast::LinearBlock &node) override
visit node of type ast::LinearBlock
int num_state_variables
count of state variables
void visit_non_linear_block(const ast::NonLinearBlock &node) override
visit node of type ast::NonLinearBlock
int num_instance_variables
count of per channel instance variables
void visit_procedure_block(const ast::ProcedureBlock &node) override
visit node of type ast::ProcedureBlock
bool under_solve_block
whether solve block is being visited
static bool is_local_variable(const std::shared_ptr< symtab::Symbol > &symbol)
bool under_net_receive_block
whether net receive block is being visited
bool start_measurement
whether to measure performance for current block
void visit_function_block(const ast::FunctionBlock &node) override
visit node of type ast::FunctionBlock
int num_constant_global_variables
subset of global variables which are constant
bool symbol_to_skip(const std::shared_ptr< symtab::Symbol > &symbol) const
Certain statements / symbols needs extra check while measuring read/write operations.
#define i
Definition: md1redef.h:19
@ BOP_EXACT_EQUAL
==
Definition: ast_common.hpp:61
@ BOP_GREATER_EQUAL
>=
Definition: ast_common.hpp:57
@ BOP_NOT_EQUAL
!=
Definition: ast_common.hpp:60
@ BOP_DIVISION
\/
Definition: ast_common.hpp:51
@ BOP_SUBTRACTION
Definition: ast_common.hpp:49
@ BOP_LESS_EQUAL
<=
Definition: ast_common.hpp:58
@ BOP_MULTIPLICATION
*
Definition: ast_common.hpp:50
#define assert(ex)
Definition: hocassrt.h:24
Helper class for printing AST in JSON form.
Item * title
Definition: model.cpp:40
const char * name
Definition: init.cpp:16
char Symbol
Definition: nrnconf.h:25
Status
state during various compiler passes
std::string to_string(const T &obj)
NmodlType
NMODL variable properties.
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
bool is_method(const std::string &name)
Check if given name is an integration method in NMODL.
static Node * node(Object *)
Definition: netcvode.cpp:291
Visitor for measuring performance related information
int find(const int, const int, const int, const int, const int)
static uint32_t value
Definition: scoprand.cpp:25
Base class for all Abstract Syntax Tree node types.
Definition: ast.hpp:52
Helper class to collect performance statistics.
Definition: perf_stat.hpp:36
int n_and
bitwise ops
Definition: perf_stat.hpp:64
int n_assign
write ops
Definition: perf_stat.hpp:41
void print(std::stringstream &stream) const
Definition: perf_stat.cpp:66
int n_div
expensive ops
Definition: perf_stat.hpp:49
std::vector< std::string > values() const
Definition: perf_stat.cpp:82
int n_int_func_call
mod functions (before/after inlining)
Definition: perf_stat.hpp:61
int n_local_read
cheap : typically local variables in mod file means registers
Definition: perf_stat.hpp:90
int n_global_read
expensive : typically access to dynamically allocated memory
Definition: perf_stat.hpp:84
int n_if
conditional ops
Definition: perf_stat.hpp:80
int n_ext_func_call
could be external math funcs
Definition: perf_stat.hpp:58
int n_add
basic ops (<= 1 cycle)
Definition: perf_stat.hpp:44
int n_gt
comparisons ops
Definition: perf_stat.hpp:68
int n_not
unary ops
Definition: perf_stat.hpp:76
std::string title
name for pretty-printing
Definition: perf_stat.hpp:38
int n_constant_read
could be optimized : access to variables that could be read-only in this case write counts are typica...
Definition: perf_stat.hpp:95
static std::vector< std::string > keys()
Definition: perf_stat.cpp:76
int n_exp
expensive functions : commonly used functions in mod files
Definition: perf_stat.hpp:53