def trans(psy): ''' Take the supplied psy object, add OpenACC directives and then enclose the whole schedule within a profiling region. :param psy: the PSy layer to transform. :type psy: :py:class:`psyclone.gocean1p0.GOPSy` :returns: the transformed PSy object. :rtype: :py:class:`psyclone.gocean1p0.GOPSy` ''' from psyclone.transformations import ProfileRegionTrans proftrans = ProfileRegionTrans() # Use the trans() routine in acc_transform.py to add the OpenACC directives psy = acc_trans(psy) invoke = psy.invokes.get('invoke_0_inc_field') schedule = invoke.schedule # Enclose everything in a profiling region newschedule, _ = proftrans.apply(schedule.children) invoke.schedule = newschedule newschedule.view() return psy
def test_omp_transform(): '''Tests that the profiling transform works correctly with OMP parallelisation.''' _, invoke = get_invoke("test27_loop_swap.f90", "gocean1.0", name="invoke_loop1") schedule = invoke.schedule prt = ProfileRegionTrans() omp_loop = GOceanOMPLoopTrans() omp_par = OMPParallelTrans() # Parallelise the first loop: sched1, _ = omp_loop.apply(schedule.children[0]) sched2, _ = omp_par.apply(sched1.children[0]) sched3, _ = prt.apply(sched2.children[0]) correct = ( " CALL ProfileStart(\"boundary_conditions_ne_offset_mod\", " "\"bc_ssh_code\", profile)\n" " !$omp parallel default(shared), private(i,j)\n" " !$omp do schedule(static)\n" " DO j=2,jstop\n" " DO i=2,istop\n" " CALL bc_ssh_code(i, j, 1, t%data, t%grid%tmask)\n" " END DO \n" " END DO \n" " !$omp end do\n" " !$omp end parallel\n" " CALL ProfileEnd(profile)") code = str(invoke.gen()) assert correct in code # Now add another profile node between the omp parallel and omp do # directives: sched3, _ = prt.apply(sched3.children[0].children[0].children[0]) code = str(invoke.gen()) correct = ''' CALL ProfileStart("boundary_conditions_ne_offset_mod", \ "bc_ssh_code", profile) !$omp parallel default(shared), private(i,j) CALL ProfileStart("boundary_conditions_ne_offset_mod", "bc_ssh_code_1", \ profile_1) !$omp do schedule(static) DO j=2,jstop DO i=2,istop CALL bc_ssh_code(i, j, 1, t%data, t%grid%tmask) END DO\x20 END DO\x20 !$omp end do CALL ProfileEnd(profile_1) !$omp end parallel CALL ProfileEnd(profile)''' assert correct in code
def add_profile_nodes(schedule, loop_class): '''This function inserts all required Profiling Nodes (for invokes and kernels, as specified on the command line) into a schedule. :param schedule: The schedule to instrument. :type schedule: :py::class::`psyclone.psyGen.Schedule` or derived class :param loop_class: The loop class (e.g. GOLoop, DynLoop) to instrument. :type loop_class: :py::class::`psyclone.psyGen.Loop` or derived class. ''' from psyclone.transformations import ProfileRegionTrans profile_trans = ProfileRegionTrans() if Profiler.profile_kernels(): for i in schedule.children: if isinstance(i, loop_class): profile_trans.apply(i) if Profiler.profile_invokes(): profile_trans.apply(schedule.children)
def test_profile_basic(capsys): '''Check basic functionality: node names, schedule view. ''' from psyclone.psyGen import colored, SCHEDULE_COLOUR_MAP Profiler.set_options([Profiler.INVOKES]) _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", "gocean1.0", idx=0) assert isinstance(invoke.schedule.children[0], ProfileNode) invoke.schedule.view() out, _ = capsys.readouterr() gsched = colored("GOInvokeSchedule", SCHEDULE_COLOUR_MAP["Schedule"]) loop = Loop().coloured_text profile = invoke.schedule.children[0].coloured_text # Do one test based on schedule view, to make sure colouring # and indentation is correct expected = (gsched + "[invoke='invoke_0', Constant loop bounds=True]\n" " " + profile + "\n" " " + loop + "[type='outer', field_space='go_cv', " "it_space='go_internal_pts']\n") assert expected in out prt = ProfileRegionTrans() # Insert a profile call between outer and inner loop. # This tests that we find the subroutine node even # if it is not the immediate parent. new_sched, _ = prt.apply( invoke.schedule.children[0].children[0].children[0]) new_sched_str = str(new_sched) correct = ("""GOInvokeSchedule(Constant loop bounds=True): ProfileStart[var=profile] GOLoop[id:'', variable:'j', loop_type:'outer'] Literal[value:'2'] Literal[value:'jstop-1'] Literal[value:'1'] Schedule: GOLoop[id:'', variable:'i', loop_type:'inner'] Literal[value:'2'] Literal[value:'istop'] Literal[value:'1'] Schedule: kern call: compute_cv_code End Schedule End GOLoop End Schedule End GOLoop GOLoop[id:'', variable:'j', loop_type:'outer'] Literal[value:'1'] Literal[value:'jstop+1'] Literal[value:'1'] Schedule: GOLoop[id:'', variable:'i', loop_type:'inner'] Literal[value:'1'] Literal[value:'istop+1'] Literal[value:'1'] Schedule: kern call: bc_ssh_code End Schedule End GOLoop End Schedule End GOLoop ProfileEnd End Schedule""") assert correct in new_sched_str Profiler.set_options(None)
def test_transform_errors(capsys): '''Tests error handling of the profile region transformation.''' # This has been imported and tested before, so we can assume # here that this all works as expected/ _, invoke = get_invoke("test27_loop_swap.f90", "gocean1.0", name="invoke_loop1") schedule = invoke.schedule prt = ProfileRegionTrans() with pytest.raises(TransformationError) as excinfo: prt.apply([schedule.children[0].children[0], schedule.children[1]]) assert "supplied nodes are not children of the same parent." \ in str(excinfo) # Supply not a node object: with pytest.raises(TransformationError) as excinfo: prt.apply(5) assert "Argument must be a single Node in a schedule or a list of Nodes " \ "in a schedule but have been passed an object of type: " \ in str(excinfo) # Python 3 reports 'class', python 2 'type' - so just check for both assert "<type 'int'>" in str(excinfo) or "<class 'int'>" in str(excinfo) # Test that it will only allow correctly ordered nodes: with pytest.raises(TransformationError) as excinfo: sched1, _ = prt.apply([schedule.children[1], schedule.children[0]]) assert "Children are not consecutive children of one parent:" \ in str(excinfo) with pytest.raises(TransformationError) as excinfo: sched1, _ = prt.apply([schedule.children[0], schedule.children[2]]) assert "Children are not consecutive children of one parent:" \ in str(excinfo) # Test 3 element lists: first various incorrect ordering: with pytest.raises(TransformationError) as excinfo: sched1, _ = prt.apply( [schedule.children[0], schedule.children[2], schedule.children[1]]) assert "Children are not consecutive children of one parent:" \ in str(excinfo) with pytest.raises(TransformationError) as excinfo: sched1, _ = prt.apply( [schedule.children[1], schedule.children[0], schedule.children[2]]) assert "Children are not consecutive children of one parent:" \ in str(excinfo) # Just to be sure: also check that the right order does indeed work! sched1, _ = prt.apply( [schedule.children[0], schedule.children[1], schedule.children[2]]) sched1.view() out, _ = capsys.readouterr() # out is unicode, and has no replace function, so convert to string first out = str(out).replace("\n", "") correct_re = (".*GOInvokeSchedule.*" r" .*Profile.*" r" .*Loop.*\[type='outer'.*" r" .*Loop.*\[type='outer'.*" r" .*Loop.*\[type='outer'.*") assert re.search(correct_re, out) # Test that we don't add a profile node inside a OMP do loop (which # would be invalid syntax): _, invoke = get_invoke("test27_loop_swap.f90", "gocean1.0", name="invoke_loop1") schedule = invoke.schedule prt = ProfileRegionTrans() omp_loop = GOceanOMPLoopTrans() # Parallelise the first loop: sched1, _ = omp_loop.apply(schedule.children[0]) # Inserting a ProfileRegion inside a omp do loop is syntactically # incorrect, the inner part must be a do loop only: with pytest.raises(TransformationError) as excinfo: prt.apply(sched1.children[0].children[0]) assert "A ProfileNode cannot be inserted between an OpenMP/ACC directive "\ "and the loop(s) to which it applies!" in str(excinfo)
def test_transform(capsys): '''Tests normal behaviour of profile region transformation.''' _, invoke = get_invoke("test27_loop_swap.f90", "gocean1.0", name="invoke_loop1") schedule = invoke.schedule prt = ProfileRegionTrans() assert str(prt) == "Insert a profile start and end call." assert prt.name == "ProfileRegionTrans" # Try applying it to a list sched1, _ = prt.apply(schedule.children) correct = ("""GOInvokeSchedule(Constant loop bounds=True): ProfileStart[var=profile] GOLoop[id:'', variable:'j', loop_type:'outer'] Literal[value:'2'] Literal[value:'jstop'] Literal[value:'1'] Schedule: GOLoop[id:'', variable:'i', loop_type:'inner'] Literal[value:'2'] Literal[value:'istop'] Literal[value:'1'] Schedule: kern call: bc_ssh_code End Schedule End GOLoop End Schedule End GOLoop GOLoop[id:'', variable:'j', loop_type:'outer'] Literal[value:'1'] Literal[value:'jstop+1'] Literal[value:'1'] Schedule: GOLoop[id:'', variable:'i', loop_type:'inner'] Literal[value:'1'] Literal[value:'istop'] Literal[value:'1'] Schedule: kern call: bc_solid_u_code End Schedule End GOLoop End Schedule End GOLoop GOLoop[id:'', variable:'j', loop_type:'outer'] Literal[value:'1'] Literal[value:'jstop'] Literal[value:'1'] Schedule: GOLoop[id:'', variable:'i', loop_type:'inner'] Literal[value:'1'] Literal[value:'istop+1'] Literal[value:'1'] Schedule: kern call: bc_solid_v_code End Schedule End GOLoop End Schedule End GOLoop ProfileEnd End Schedule""") assert correct in str(sched1) # Now only wrap a single node - the middle loop: sched2, _ = prt.apply(schedule.children[0].children[1]) correct = ("""GOInvokeSchedule(Constant loop bounds=True): ProfileStart[var=profile] GOLoop[id:'', variable:'j', loop_type:'outer'] Literal[value:'2'] Literal[value:'jstop'] Literal[value:'1'] Schedule: GOLoop[id:'', variable:'i', loop_type:'inner'] Literal[value:'2'] Literal[value:'istop'] Literal[value:'1'] Schedule: kern call: bc_ssh_code End Schedule End GOLoop End Schedule End GOLoop ProfileStart[var=profile_1] GOLoop[id:'', variable:'j', loop_type:'outer'] Literal[value:'1'] Literal[value:'jstop+1'] Literal[value:'1'] Schedule: GOLoop[id:'', variable:'i', loop_type:'inner'] Literal[value:'1'] Literal[value:'istop'] Literal[value:'1'] Schedule: kern call: bc_solid_u_code End Schedule End GOLoop End Schedule End GOLoop ProfileEnd GOLoop[id:'', variable:'j', loop_type:'outer'] Literal[value:'1'] Literal[value:'jstop'] Literal[value:'1'] Schedule: GOLoop[id:'', variable:'i', loop_type:'inner'] Literal[value:'1'] Literal[value:'istop+1'] Literal[value:'1'] Schedule: kern call: bc_solid_v_code End Schedule End GOLoop End Schedule End GOLoop ProfileEnd End Schedule""") assert correct in str(sched2) # Check that an sublist created from individual elements # can be wrapped sched3, _ = prt.apply( [sched2.children[0].children[0], sched2.children[0].children[1]]) sched3.view() out, _ = capsys.readouterr() # .replace("\n", "") # out is unicode, and has no replace function, so convert to string first out = str(out).replace("\n", "") correct_re = (".*GOInvokeSchedule.*" r" .*Profile.*" r" .*Profile.*" r" .*Loop.*\[type='outer'.*" r" .*Profile.*" r" .*Loop.*\[type='outer'.*" r" .*Loop.*\[type='outer'.*") assert re.search(correct_re, out)
# POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: A. R. Porter, STFC Daresbury Lab '''Module containing py.test tests for the transformation of the PSy representation of NEMO code to insert profiling calls. ''' from __future__ import absolute_import, print_function import pytest from psyclone.psyGen import PSyFactory from psyclone.transformations import TransformationError, ProfileRegionTrans from fparser.common.readfortran import FortranStringReader # The transformation that most of these tests use PTRANS = ProfileRegionTrans() def get_nemo_schedule(parser, code): ''' Utility to construct the PSyIR of the supplied Fortran code. :param parser: the Fortran parser to use. :type parser: :py:class:`fparser.two.Fortran2003.Program` :param str code: the Fortran code to process. :returns: 2-tuple of the top-level PSy object and the Schedule of \ the first Invoke (routine) in `code`. :rtype: (:py:class:`psyclone.nemo.NemoPSy`, \ :py:class:`psyclone.nemo.NemoInvokeSchedule`) '''
def test_profile_basic(capsys): '''Check basic functionality: node names, schedule view. ''' Profiler.set_options([Profiler.INVOKES]) _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", "gocean1.0", idx=0) assert isinstance(invoke.schedule.children[0], ProfileNode) invoke.schedule.view() out, _ = capsys.readouterr() coloured_schedule = GOSchedule([]).coloured_text coloured_loop = Loop().coloured_text coloured_kern = GOKern().coloured_text coloured_profile = invoke.schedule.children[0].coloured_text # Do one test based on schedule view, to make sure colouring # and indentation is correct correct = ( '''{0}[invoke='invoke_0',Constant loop bounds=True] {3} {1}[type='outer',field_space='go_cv',it_space='go_internal_pts'] {1}[type='inner',field_space='go_cv',it_space='go_internal_pts'] {2} compute_cv_code(cv_fld,p_fld,v_fld) ''' '''[module_inline=False] {1}[type='outer',field_space='go_ct',it_space='go_all_pts'] {1}[type='inner',field_space='go_ct',it_space='go_all_pts'] {2} bc_ssh_code(ncycle,p_fld,tmask) ''' '''[module_inline=False]'''.format(coloured_schedule, coloured_loop, coloured_kern, coloured_profile) ) assert correct in out prt = ProfileRegionTrans() # Insert a profile call between outer and inner loop. # This tests that we find the subroutine node even # if it is not the immediate parent. new_sched, _ = prt.apply(invoke.schedule.children[0] .children[0].children[0]) new_sched_str = str(new_sched) correct = ("""GOSchedule(Constant loop bounds=True): ProfileStart[var=profile] Loop[]: j= lower=2,jstop-1,1 ProfileStart[var=profile_1] Loop[]: i= lower=2,istop,1 kern call: compute_cv_code EndLoop ProfileEnd EndLoop Loop[]: j= lower=1,jstop+1,1 Loop[]: i= lower=1,istop+1,1 kern call: bc_ssh_code EndLoop EndLoop ProfileEnd End Schedule""") assert correct in new_sched_str Profiler.set_options(None)