fparser Reference Guide  0.0.14
fparser2_bench.py
1 #!/usr/bin/env python
2 # Copyright (c) 2022 Science and Technology Facilities Council
3 #
4 # All rights reserved.
5 #
6 # Modifications made as part of the fparser project are distributed
7 # under the following license:
8 #
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions are
11 # met:
12 #
13 # 1. Redistributions of source code must retain the above copyright
14 # notice, this list of conditions and the following disclaimer.
15 #
16 # 2. Redistributions in binary form must reproduce the above copyright
17 # notice, this list of conditions and the following disclaimer in the
18 # documentation and/or other materials provided with the distribution.
19 #
20 # 3. Neither the name of the copyright holder nor the names of its
21 # contributors may be used to endorse or promote products derived from
22 # this software without specific prior written permission.
23 #
24 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 
36 """
37 Generates a large Fortran program in memory and then measures how long
38 it takes fparser2 to parse it. This is based on the benchmark suggested
39 by Ondřej Čertík via Ioannis Nikiteas.
40 
41 """
42 from time import perf_counter
43 
44 from fparser.common.sourceinfo import FortranFormat
45 from fparser.common.readfortran import FortranStringReader
46 from fparser.two.parser import ParserFactory
47 
48 
49 def gen_sub(num: int):
50  """
51  Constructs a Fortran subroutine named g<num>.
52 
53  :param num: the number of the subroutine (used to name it).
54 
55  :returns: Fortran subroutine.
56  :rtype: str
57 
58  """
59  sub = f"""subroutine g{num}(x)
60  integer, intent(inout) :: x
61  integer :: i
62  x = 0
63  do i = {num}, {num+9}
64  x = x+i
65  end do
66 end subroutine
67 
68 """
69  return sub
70 
71 
72 def create_bench(num_routines: int):
73  """
74  Creates the Fortran benchmark code.
75 
76  :param num_routines: the number of subroutines to create.
77 
78  :returns: benchmark Fortran code.
79  :rtype: str
80 
81  """
82  code = ["program bench3", "implicit none", "integer :: c", "c = 0"]
83  for i in range(1, num_routines + 1):
84  code.append(f"call g{i}(c)")
85 
86  code.append("print *, c")
87  code.append("contains")
88 
89  for i in range(1, num_routines + 1):
90  code.append(gen_sub(i))
91  code.append("end program")
92 
93  return "\n".join(code)
94 
95 
96 def runner(num_routines: int):
97  """
98  Entry point for running the benchmark.
99 
100  :param num_routines: the number of subroutines to create in the \
101  Fortran benchmark.
102 
103  :raises ValueError: if num_routines < 1.
104 
105  """
106  if num_routines < 1:
107  raise ValueError(
108  f"Number of routines to create must be a positive, "
109  f"non-zero integer but got: {num_routines}"
110  )
111 
112  print(f"Constructing benchmark code with {num_routines} subroutines...")
113  code = create_bench(num_routines)
114  reader = FortranStringReader(code)
115  # Ensure the reader uses free format.
116  reader.set_format(FortranFormat(True, True))
117 
118  fparser = ParserFactory().create()
119 
120  print("Parsing benchmark code...")
121  tstart = perf_counter()
122  _ = fparser(reader)
123  tstop = perf_counter()
124 
125  print(f"Time taken for parse = {tstop - tstart:.2f}s")
126 
127 
128 if __name__ == "__main__":
129  runner(10000) # pragma: no cover