Remove #ifdefs in favor of run time logical flags #763
Replies: 17 comments 9 replies
-
Hello, I am one of the people that pushed some years ago for the use of #ifdef statements so I thought I ought to comment on that:
About the proposal:
Final thoughts:
|
Beta Was this translation helpful? Give feedback.
-
@MathieuDutSik - thank you for your thoughtful comments. I'd like to respond to some of the points you raised. I view #ifdef statements as a necessary evil. It makes the code unclear that is true, but if we want to have fast code, they are useful I believe.
Removing completely #ifdef statements would require something like making PDLIB mandatory which may be a good idea but something that requires a lot of issues one by one. Putting the choice in a namelist gives the possibility of runtime loss.
Proposition 6 "Remove or fix #ifdefs which result in compile errors when other #ifdefs are set" is also good. But I orient myself more toward fix than remove.
Some of the statements are indeed bad like "#ifdef COND1 #ifdef COND2" while a better one would be "#if defined COND1 && defined COND2"
|
Beta Was this translation helpful? Give feedback.
-
@MathieuDutSik - I've done a quick overview of the number of #ifdefs in CICE6 and MOM6 compared to ww3 and this is what I am seeing:
|
Beta Was this translation helpful? Give feedback.
-
In FV3-ATM (which includes the FV3 dynamic core and CCPP physics), there are about 1000 lines of #ifdef. Of those it appears that about two dozen are unique. |
Beta Was this translation helpful? Give feedback.
-
@MathieuDutSik - #ifdefs greatly increase the cost of testing code which leads to a greatly diminished reliability of the code. Given limited resources for testing I would advocate that this is a drawback of the current WW3 approach. I used examples of modern new code bases that have strongly stayed away from #ifdefs from the onset. |
Beta Was this translation helpful? Give feedback.
-
Thanks @MathieuDutSik for your engagement and comments. I understand some of your concerns (eg. impact on performance) but I do think the WW3 community should be concerned about more than just operational implementations. Where do improvements to operational implementations come from if not the development community? My primary area of concern is the coupled applications in the UFS. We provide multiple configurations and applications to the UFS user community ---from standalone FV3 to fully coupled ATM-OCN-ICE-WAV-AEROSOL configurations. WW3 is the only component for which we cannot have a single compile and then multiple physics/feature tests. We cannot provide the UFS community access to WW3's features, which we must test on an on-going basis, when each flavor of WW3 requires it's own unique application--from compile to mod_def and so on. It is not just about the compile time. Without the capability to easily test multiple flavors of WW3, we simply can't provide the UFS community with a feature rich WW3 component. I grant you that as newcomers to the code, Mariana and I see WW3 with a different eye. But I think that WW3 does developers a mis-service when code readability is so poor. It really places an undue burden on developers to easily engage with the code. Also, I'm not sure I understand your distinction between developer and user. Shouldn't a user be able to understand what the developer has done? In my opinion, the user is likely to be even more impacted by poor code readability. In our experience, there are multiple good reasons why other community-based models (even operational ones) rely on a minimum number of #ifdefs. And, no, it is not just about counting ifdefs---the point was to illustrate the degree to which they're being over-used in WW3 by referencing other complex and feature-rich models which have to a large degree abandoned the ifdef solution. |
Beta Was this translation helpful? Give feedback.
-
Hi all, just thought I'd offer my two cents: I don't have experience with operational performance constraints, other than knowing they are very stringent. That being said, a 2% performance difference seems to be within-bounds of the expected variability between runs of the same simulation on a given machine. It's my general feeling that as long as logic statements are used following reasonable best-practices (e.g not positioned within the inner-most computational loops, no string comparisons, etc.), their performance impact should be extremely low with modern compilers. |
Beta Was this translation helpful? Give feedback.
-
I agree with the comments above regarding having a limited amount of "necessary evil" #ifdefs in the code. Looking at the regression tests, one of the most common differences between test in the same group is changes in the source term package (STx). Perhaps a good starting point would be a refactor of the W3SRCE module to use conditionals rather than #IFDEFS for the source term selection? There are many shared variables in that module and since all the individual source term packages are contained in separate subroutines I think it would have minimal performance impact. Also, it would clean up the W3SRCE module significantly, which would be a really good thing as all the #ifdefs make it a very quite difficult to follow the code... |
Beta Was this translation helpful? Give feedback.
-
slides |
Beta Was this translation helpful? Give feedback.
-
I agree with @ukmo-ccbunney that perhaps source term packages would be a good place to start. |
Beta Was this translation helpful? Give feedback.
-
@DeniseWorthen and @mvertens thank you so much for starting this conversation and your presentation today. To move things forward, we'd ask that comments on this topic be posted by Sunday 9/11/22 so that a path forward can be determined and announced by Wednesday 9/14/22. |
Beta Was this translation helpful? Give feedback.
-
@DeniseWorthen Do you envisage this being made as a single large change, or a piecemeal change over several PRs? |
Beta Was this translation helpful? Give feedback.
-
@ukmo-ccbunney I think regardless of how far down the path of ifdef removal path is finally decided up (ie, runtime config), the step which is required regardless is a cleanup. The python script that @mvertens wrote can use a subset of #ifdef statements. So, for example, you could start with just the 'debug' type ifdefs. You could stage the changes over several PRs by dividing the files into groups for example. There is definitely a trade-off between limiting changes and dragging out the process. Now, converting the 3 lines of an #ifdef to three lines of if(..flag) gets you only so far. But when we were prototyping this process, once many of these "debug" ifdefs got converted, the serial and other granular ifdefs become much more visible. It was much easier to spot and do clean-up at that point. But that is essentially a manual process; some files have a lot of required clean-up and others not much. I do think it is really important to set out ahead of time the desired level of debug-type statements. The other comment I'd make about staging, is that the overall process is much easier once the indenting issue is fixed. When you're trying to clean up ifdefs, it can get very difficult over 100s of lines if the endif you're hunting for isn't where you expect it to be. That is how these two issues (ifdefs, code standards) became somewhat linked. So, in my perfect world, the F90-free format + indenting changes + unwrapping lines (which would be almost all whitespace changes) would come in first. In our experience that makes the next step much easier. That next step would be primarily an ifdef-cleanup via the use of logical flags for a sub-set of all ifdefs, most like in chunked file sets. |
Beta Was this translation helpful? Give feedback.
-
Hi there, I am in favour of reducing the number of ifdefs to a minimum and the best way to start is with source terms as pointed out by Chris Bunney. Beyond that, I think a code review is necessary because from experience some switched won't work or are compatible with other. For example SMC, ARCTIC and PDLIB for unstructured mesh will generate a different code base and won't work in ww3_multi as intended with rectilinear , tripolar, curvilinear grids. |
Beta Was this translation helpful? Give feedback.
-
I second @stefanzieger's comment. |
Beta Was this translation helpful? Give feedback.
-
For those concerned about the disruption of the a potential large change and how that impacts their own development work: We will give a minimum of two weeks notice to allow those with ongoing work to submit PRs (similar to when we went from switches to ifdefs). If you need more than 2 weeks, please let us know and we can start to plan and work to minimize the disruption to development. |
Beta Was this translation helpful? Give feedback.
-
There is support for moving forward with this proposal. There seem to be a few major concerns:
However, the benefits of this change outweigh the cons. To address the major concerns we will do the following:
Testing of a PR with related updates:
|
Beta Was this translation helpful? Give feedback.
-
Issue
Currently WW3 uses #ifdefs to control the physics or other configurable features. In modern F90 code, #ifdefs are largely avoided and instead such options are controlled at run time via the use of logical flags. The main disadvantages of #ifdefs are:
Compile-time dependence of features or physics options. This reduces flexibility because code must be recompiled to exercise optional features. (In the case of WW3, true run-time control would probably also require modification to the contents of the mod_def file.)
Poor Code Readability. Developers cannot easily determine which section of code will execute. The impact is especially acute when the developer encounters nested #ifdefs or branching if-endif logic within an #ifdef.
Code becomes unnecessarily long when overused or used when not needed.
Testing of all possible permutations of #ifdefs is not possible. Many ifdefs only make sense when other #ifdefs are also set and conflict when a third #ifdef is set.
Developers are incentivized to "wall off" their changes instead of utilizing more extensible code. For example, passing an argument under #ifdef control instead of using the optional construct. This results in less extensible code.
Proposal
The use of #ifdefs within WW3 should be minimized to the extent possible. The transition can be accomplished by including a temporary file which would define #ifdefs at compile time to set a matching logical flag. This code would eventually be removed and the logical flags moved to a namelist structure.
The removal of #ifdefs has been automated using a python script developed by @mvertens. This script also leverages emacs to apply standard F90 indenting and syntax. This standardization of syntax will be further explored within the existing discussion #551.
The proposal to remove #ifdefs include the following changes:
Remove unneccessary ifdefs. #ifdefs are not required in the following cases
in most use statements
in definition of local variables
in definitions of derived types
around format statements
around most write statements
around OpenMP blocks
Reduce granularity of existing "debug" type #ifdefs (eg. debuginit, debugrun etc). Such statements should be retained only at the enter/exit stage of a subroutine and at the enter/exit stage of major code blocks (e.g. "3.1 Interpolate winds and currents"). Highly granular debug statements can be added by developers during debugging but should not be committed.
In many cases, serial ifdefs only become apparent after nested "debug" ifdefs are replaced with logical flags. Thus, pruning the use of "debug" ifdefs not only allows serial #ifdefs to be removed, it also improves both code readability can significantly reduce code length. For example, in w3wdatmd:
WW3/model/src/w3wdatmd.F90
Line 529 in 67d7715
Pruning the #ifdef W3_DEBUGINIT lines and collecting the remaining #ifdefs results in
This equivalent code is more compact (21 lines vs 74) and much more clearly shows what is being done within the PDLIB ifdef.
For example, in w3wavemd.F90:
WW3/model/src/w3wavemd.F90
Line 3284 in 67d7715
can be replaced with
WW3/model/src/wminitmd.F90
Line 2834 in 67d7715
Where the ENDIF at LN 2855 matches either #ifdef W3_SHRD or W3_MPI.
Beta Was this translation helpful? Give feedback.
All reactions