Skip to content

optarch can now be specified on a toolchain basis#2071

Merged
boegel merged 20 commits intoeasybuilders:developfrom
damianam:optarch
Jan 27, 2017
Merged

optarch can now be specified on a toolchain basis#2071
boegel merged 20 commits intoeasybuilders:developfrom
damianam:optarch

Conversation

@damianam
Copy link
Copy Markdown
Member

This PR enables EB to accept a more powerful --optarch syntax. Valid options are:

--optarch=someflag -> Same as now
--optarch=GENERIC -> Same as now
--optarch=Intel:xCORE-AVX512,GCC:GENERIC -> Tells Intel based toolchains to use -xCORE-AVX512 as a flag, and GCC based toolchains to use a generic set of flags (as specified in the toolchain definition)
--optarch=Intel:,GCC:someflag -> Tells Intel based toolchains to have the default behaviour (currently -xHost), and GCC based toolchains to use -someflag
--optarch=Intel,GCC:someflag -> Same as the previous case
--optarch=Intel:xCORE-AVX512,GCC:GENERIC,Intel:xHost -> Tells Intel based toolchains to use -xCORE-AVX512. The second case is ignored.

This allows to install packages on multiple toolchains using the correct --optarch option in a single command, or to have a single definition of EASYBUILD_OPTARCH that is correct regardless of the toolchain used.

A test is pending.

@boegel
Copy link
Copy Markdown
Member

boegel commented Dec 29, 2016

@damianam A couple of quick thoughts:

@damianam
Copy link
Copy Markdown
Member Author

  • I'll look into the strlist suggestion
  • I'll fix the --optarch=Intel:'' option. I didn't noticed that you could force it to don't add any flag.
  • My mindset regarding listing a toolchain but without any particular option is to simply make it explicit that no special option is wanted for that toolchain.
  • The tests are pending as I said in the PR ;-P. I wanted to get some feedback first, and get it out before taking a look at the tests
  • No problem with the documentation, I can update it once this gets merged.

@damianam
Copy link
Copy Markdown
Member Author

Now --optarch=Intel: and --optarch=Intel:'' effectively disable any optarch flag. --optarch=Intel does nothing.

@boegel
Copy link
Copy Markdown
Member

boegel commented Dec 29, 2016

@damianam but the behaviour of using Intel: changes when combined with GCC:foo?

@damianam
Copy link
Copy Markdown
Member Author

@boegel no, it behaves the same:

eb darshan-runtime-3.1.1-ipsmpi-2016b.eb darshan-runtime-3.1.1-gpsmpi-2016b.eb --extended-dry-run  --optarch=Intel: | grep "export CFLAGS"
  export CFLAGS="-O2 -ftz -fp-speculation=safe -fp-model source"
  export CFLAGS="-O2 -march=native"
eb darshan-runtime-3.1.1-ipsmpi-2016b.eb darshan-runtime-3.1.1-gpsmpi-2016b.eb --extended-dry-run  --optarch=Intel:,GCC:GENERIC | grep "export CFLAGS"
  export CFLAGS="-O2 -ftz -fp-speculation=safe -fp-model source"
  export CFLAGS="-O2 -march=x86-64 -mtune=generic"
eb darshan-runtime-3.1.1-ipsmpi-2016b.eb darshan-runtime-3.1.1-gpsmpi-2016b.eb --extended-dry-run  --optarch=Intel,GCC:GENERIC | grep "export CFLAGS"
  export CFLAGS="-O2 -xHost -ftz -fp-speculation=safe -fp-model source"
  export CFLAGS="-O2 -march=x86-64 -mtune=generic"
eb darshan-runtime-3.1.1-ipsmpi-2016b.eb darshan-runtime-3.1.1-gpsmpi-2016b.eb --extended-dry-run  --optarch=GCC:GENERIC | grep "export CFLAGS"
  export CFLAGS="-O2 -xHost -ftz -fp-speculation=safe -fp-model source"
  export CFLAGS="-O2 -march=x86-64 -mtune=generic"

@boegel boegel added this to the 3.1.0 milestone Dec 30, 2016
@damianam
Copy link
Copy Markdown
Member Author

damianam commented Jan 7, 2017

@boegel I changed the option type. I'll take a look to the tests soon[ish].

@boegel boegel changed the title optarch can now be specified on a toolchain basis. optarch can now be specified on a toolchain basis (WIP) Jan 9, 2017
@boegel
Copy link
Copy Markdown
Member

boegel commented Jan 9, 2017

@damianam please remove the (WIP) tag in the PR title when you consider this good to go

@damianam damianam changed the title optarch can now be specified on a toolchain basis (WIP) optarch can now be specified on a toolchain basis Jan 11, 2017
@damianam
Copy link
Copy Markdown
Member Author

@boegel I think the PR is ready now. Please review

@damianam
Copy link
Copy Markdown
Member Author

I've noticed a few caveats:

  • Clang won't accept this syntax because it doesn't set COMPILER_FAMILY (!?!?). Maybe we should use the compiler class name instead? That doesn't seem very clean though. I suggest to actually assign a COMPILER_FAMILY to Clang
  • Currently I am using PGI as a failsafe check in the tests, to check that nothing in particular is done to its settings if it is not explicitly listed. The issue here is that by default the optimal architecture setting is simply not setting anything (ie: ''), and therefore I can't check that xFLAGS are correctly set ('' in any_string is always True). I'd gladly change the check to clang but we need to fix the first issue first.

@boegel
Copy link
Copy Markdown
Member

boegel commented Jan 12, 2017

  • We haven't considered Clang a full compiler family up until now because it doesn't include a Fortran compiler (all other compiles we support do), but I guess that's not a very compelling argument... I'm OK with defining COMPILER_FAMILY for Clang by itself.

  • See inline comment for the check involving an empty string.

Comment thread easybuild/tools/options.py Outdated
'mpi-cmd-template': ("Template for MPI commands (template keys: %(nr_ranks)s, %(cmd)s)",
None, 'store', None),
'mpi-tests': ("Run MPI tests (when relevant)", None, 'store_true', True),
# optarch is in most cases equivalent to strlist, but changing the type breaks compatibility
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, I think this comment would be more confusing in the long run, it's better to mention this where we split the optarch value by ,?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. I'll move it in the next commit

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread easybuild/tools/toolchain/compiler.py Outdated
# Option is generic, so mark it as to be set later and stop iterating
elif optarch_option == OPTARCH_GENERIC:
use_generic = True
break
Copy link
Copy Markdown
Member

@boegel boegel Jan 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this approach, a value like --optarch=GENERIC,intel:foo would be equivalent to --optarch=GENERIC, while --optarch=intel:foo,GENERIC would not be, right? That seems very confusing to me...

I think we should do some validation here, and either except --optarch=GENERIC, --optarch=<flags> or --optarch=<flags-by-compiler>, and not allow mixing; if you detect a dict-like value, we should validate that all entries really specify a mapping, and not make things too flexible (since that makes it too easy to shoot yourself in the foot and end up with a configuration that doesn't do what you think it does).

Another problem I'm concerned with is how to discriminate between --optarch=<flags> where the , character is included in the list of flags (e.g. -O2 -Wl,-Bstatic), and --optarch=<flags-by-compiler>, where the entries for the different compiler are separated by a ,.

So, I think we should go with a more 'obscure' separation character rather than ,, something like | or @ or &, i.e. stuff that is a lot less likely to appear in a set of compiler flags.
The same goes for the : character that is now used to separate the compiler name from the set of flags to use for that compiler.

Either that, or let --optarch accept the path to a file that specifies to compiler-to-flag mapping in some syntax that's easy to parse (e.g. .ini format like we have for external modules metadata, see http://easybuild.readthedocs.io/en/latest/Using_external_modules.html#metadata-for-external-modules).

I actually strongly prefer the latter, since that makes things a lot easier; you can just check whether --optarch points to an existing file, and fall back to what we do now if it doesn't. Parsing a dict-like value from a string is just painful, and it's probably better to avoid that?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right in the first part of the comment. My mind set was to accept the "first match" and properly document it. Even validating every entry you need something like that to deal with things like --optarch=Intel:xHost,GCC:something,Intel:xSSE2.

I think that like it is now is not just more powerful, but also easier to implement (the validation requires a couple of lines more of code, even though it is not significant). It comes with a cost, which is lack of strictness (sounds worse than flexibility :-P). However I have no strong feelings about doing it like you suggest. Just let me know which direction you want to take.

Regarding the parsing bit and the separators: Couldn't we use some similar library to parse that? Even without it, it shouldn't be too complicated to accept something like --optarch=Intel:'O2 -Wl,-Bstatic',GCC:something.

Beyond that, do we really need that capability? I mean, none of the compilers I know require it (see list below), and if an user wants to put flags in --optarch that don't belong there, should we get out of our way to support that? In my opinion we should support that just if a compiler requires it.

  • Intel: -xHost or -xAVX2, etc
  • GCC: -march=x86-64 -mtune=generic
  • PGI: -tp=haswell
  • clang: -march=x86-64 -mtune=generic
  • IBM XL: -qtune=auto

And just as I finish typing this I see that nvcc accepts things like --gpu-architecture=compute_50 --gpu-code=sm_50,compute_50 and my argument falls apart and goes down the drain. So, coming back to the previous point, do we accept something like --optarch=Intel:'O2 -Wl,-Bstatic',GCC:something, or do we change the characters? Changing them is trivial so I am ok with it.

Last but not least: I strongly oppose the configuration file idea. My arguments are:

  1. Disrupts our workflow. We do this sort of tuning with environment variables that are set in configuration modules. We just use files for python code (custom easyblocks, toolchains, MNS)
  2. It is kind of overkill. The longest string I can think of is something like Intel:xCORE-AVX2,GCC:-march=x86-64 -mtune=generic,PGI:-tp=haswell,CLANG:-march=x86-64 -mtune=generic,IBMXL:-qtune=auto,CUDA:--gpu-architecture=compute_50 --gpu-code=sm_50,compute_50. This is an exaggeration, but even exaggerating it, it is pretty manageable.

I would be willing to accept a file as an additional option, if it fits better in people's workflow, but not excluding the --optarch=Compiler1:flag1,Compiler2:flag2 syntax

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that being too flexible may be an issue too, since it makes it too easy to have eb do something while you're expecting something else. Even if this is clearly documented, we should try and avoid surprises, and do a bit of input validation to protect against silly mistakes (we all make them).

I'm OK with your opposition against using (only) a file, I understand the argument. Also supporting a file can always be added later.

The easiest way to go forward seems to be:

  • use different characters than , and : to specify the mapping of compiler to flags (the nvcc flags are an excellent example); wrapping flags in single quotes is not something I'd go for, since you need to be quite careful to make sure bash doesn't eat your single quotes...

I would also input validation, see my previous comment and add to that to not allow includes the same compiler twice (like in your Intel:...,...,Intel:.... example), there's no reason we should allow that, it will only lead to confusion. And it's fairly easy to check.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, changed to : and ;, and now it is more strict

Comment thread easybuild/tools/toolchain/compiler.py Outdated
current_compiler = getattr(self, 'COMPILER_FAMILY', None)
comp = optarch_option.split(":")[0]
comp_optarch = optarch_option.split(":")[1] if len(optarch_option.split(":")) > 1 else None
if comp == current_compiler:
Copy link
Copy Markdown
Member

@boegel boegel Jan 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're now both parsing the --optarch value and checking for a match in one go, it would make more sense to do the parsing a lot earlier (when initialising the toolchain), to catch any formatting problems with the specified --optarch value early on

the actual checking of which flags to use from that parsed value should still occur here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmmmh. It looks reasonable in principle but following that approach I think it would make more sense to have an input sanitizing step for all the options. Is there something like that?

Otherwise seems kind of unnatural, this option would be the only one that gets parsed before it is actually processed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have type checking support for easyconfig parameters, but not for configuration options.

We do a little bit of option checking in the postprocess method in options.py (see e.g. the check w.r.t. module syntax), but not to a big extent. That would be the right location for input validation though.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, done

Comment thread test/framework/toolchain.py Outdated
build_options = {'optarch': optarch_var}
init_config(build_options=build_options)
for toolchain, toolchain_version in [('iccifort','2011.13.367'),('GCC','4.7.2'),('PGI','16.7-GCC-5.4.0-2.26')]:
for enable in [True, False]:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, a quadruple for-loop is a bit hard to understand...

Can't we construct one large list of test cases outside of the for loop, and then just have a single for loop over those?

That would also help with avoiding the deep indenting here...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean manually constructing that list of 54 elements, or construct the list in 4 loops, and then iterate over it?

In any case, they are 4 loops but fairly simple

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I mean creating 4 separate lists, and then letting Python create a list of 4-tuples with all possible options (seems like itertools.product does what you need).

Anyway, this is less important, since this is only a test where code style is less of an issue

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignored, at least for the moment :-P

Comment thread test/framework/toolchain.py Outdated
flag = gcc_expanded_flags
else: # PGI as an example of compiler not set
# default optarch flag
flag = tc.COMPILER_OPTIMAL_ARCHITECTURE_OPTION[(tc.arch,tc.cpu_family)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be handled outside the for loop, where you construct a list of test cases?

Also, hardcode the value for PGI rather than grabbing it from the Toolchain instance?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I hardcode it it would break if one day changes. A change like that should be irrelevant for the test.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If something changes in framework that affects this test, we can update the test accordingly.

I'd rather be aware of the impact of a change in framework.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread test/framework/toolchain.py Outdated
self.assertTrue(flag == flags, "optarch: True means '%s' == '%s'" % (flag, flags))

# Also check that it is correctly passed to xFLAGS, honoring 'enable'
if flag != '':
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if flag is == '', no checks at all?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only way I can think of to check when flag is == '' is to have a blacklist and check against it, which is kind of overkill I think....

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, we should have an else in any case?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@damianam
Copy link
Copy Markdown
Member Author

damianam commented Jan 20, 2017

@boegel I've implemented most of the discussed changes. From my perspective there is just one thing remaining: Making test to make sure that the parsing is done correctly, not just checking for exceptions.

And waiting for travis to report on all the tests, I haven't run them myself, just the new ones.

Comment thread easybuild/tools/options.py Outdated
from easybuild.tools.run import run_cmd
from easybuild.tools.package.utilities import avail_package_naming_schemes
from easybuild.tools.toolchain.compiler import DEFAULT_OPT_LEVEL, Compiler
from easybuild.tools.toolchain.compiler import DEFAULT_OPT_LEVEL, OPTARCH_TOOLCHAIN_DELIMITER, OPTARCH_OPTION_DELIMITER, Compiler
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@damianam line too long

Comment thread easybuild/tools/options.py Outdated
# make sure --optarch has a valid format
if self.options.optarch:
# optarch is in most cases equivalent to strlist, but changing the type breaks compatibility, so we hack it here
n_compilers = len(self.options.optarch.split(OPTARCH_TOOLCHAIN_DELIMITER))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put this whole block into a ('private') dedicated method, i.e. self._postprocess_optarch()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to the split once, and save it in a variable you reuse later:

optarch_parts = self.options.optarch.split(OPTARCH_TOOLCHAIN_DELIMITER)

Comment thread easybuild/tools/options.py Outdated

# make sure --optarch has a valid format
if self.options.optarch:
# optarch is in most cases equivalent to strlist, but changing the type breaks compatibility, so we hack it here
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we should include this comment, since we now have other reasons to take this approach, e.g. strlist only supports using , as a separator, while we're using ;

Comment thread easybuild/tools/options.py Outdated
# if there are options for different compilers, we set up a dict
if OPTARCH_OPTION_DELIMITER in self.options.optarch:
optarch_dict = dict()
for compiler, compiler_opt in [i.split(OPTARCH_OPTION_DELIMITER) for i in self.options.optarch.split(OPTARCH_TOOLCHAIN_DELIMITER)]:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reuse optarch_parts here, and I'd use x rather than i (i is usually an index)

Comment thread easybuild/tools/options.py Outdated
raise EasyBuildError("The optarch option has an incorrect syntax: %s" % self.options.optarch)
else:
# if there are options for different compilers, we set up a dict
if OPTARCH_OPTION_DELIMITER in self.options.optarch:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be if OPTARCH_OPTION_DELIMITER in optarch_parts[0]?

Comment thread easybuild/tools/toolchain/compiler.py Outdated
# no --optarch specified, no default value specified
elif (self.arch, self.cpu_family) in (self.COMPILER_OPTIMAL_ARCHITECTURE_OPTION or []):
# no --optarch specified, no option found for the current compiler, and no default optarch
elif use_generic == False and optarch is None and (self.arch, self.cpu_family) in (self.COMPILER_OPTIMAL_ARCHITECTURE_OPTION or []):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you know use_generic is False, otherwise you could never reach here

Comment thread easybuild/tools/toolchain/compiler.py Outdated
raise EasyBuildError("optarch is neither an string or a dict. This should never ever happen")

if optarch is None and use_generic == False:
self.log.info("_set_optimal_architecture: no optarch found for compiler %s. Ignoring option." % current_compiler)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.log.info("...", current_compiler)

(replace % with, )

Comment thread test/framework/options.py Outdated
self.assertTrue(re.search('^\s+\* GCC v4.6.3: dummy', txt, re.M))
self.assertFalse(re.search('gzip', txt, re.M))

def test_optarch(self):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call this test_parse_optarch?

Comment thread test/framework/options.py Outdated
error_msg = "The optarch option contains duplicated entries for compiler"
self.assertErrorRegex(EasyBuildError, error_msg, self.eb_main, args, raise_error=True)

# TODO: Check for correct parsing and dictionary creation
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes please :)

also check with a list of flags, GENERIC, ...

Comment thread test/framework/toolchain.py Outdated
if enable:
self.assertTrue(flag in tc.get_variable(var), "optarch: True means '%s' in '%s'" % (flag, tc.get_variable(var)))
else:
self.assertFalse(flag in tc.get_variable(var), "optarch: False means no '%s' in '%s'" % (flag, tc.get_variable(var)))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

several lines are way too long here, partially due to the deep indent...

also, just do the tc.get_variable(var)s once, above the if enable...

@damianam
Copy link
Copy Markdown
Member Author

@boegel I addressed all your comments, even the one that altered the logic for the code avoiding calling too many times build_option('optarch'). The only thing missing as far as I can see is completing the tests of options.py

@damianam
Copy link
Copy Markdown
Member Author

@boegel I think the PR is ready for a final review. I am only wondering if there is a better way to test that EasyBuild doesn't generate exceptions when it shouldn't, than simply run it and see what happens. Is there some sort of asssertNoError?

Comment thread test/framework/options.py Outdated
self.assertErrorRegex(EasyBuildError, error_msg, self.eb_main, args, raise_error=True)

# Check that it doesn't raise an exception when the input is correct
test_easyconfig = 'easyconfigs/test_ecs/t/toy/toy-0.0.eb'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@damianam please specify the full path here, usually like this:

test_ecs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs')
toy_ec = os.path.join(test_ecs, 't', 'toy', 'toy-0.0.eb')

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmmh, ok, but can I ask why?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main reason to use the full path to toy-0.0.eb is to make sure the test always runs, also if you're running it from a different location.

This test was failing on Travis with

EasyBuildError: "Can't find path /home/travis/easyconfigs/test_ecs/t/toy/toy-0.0.eb"

It probably worked for you because you were running the tests from the test/framework subdirectory?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep

Comment thread test/framework/options.py Outdated
['--optarch=Intel:;GCC:somethingelse;',test_easyconfig]
]
for args in test_cases:
self.eb_main(args, raise_error=True)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@damianam this doesn't seem like a very useful test, you're not actually checking anything here (other than the lack of crashing)...

Why not test with an existing test easyconfig that uses GCC as a toolchain (which is a lot better than using the dummy toolchain where <toolchain>.prepare() is never called...), and also include --extended-dry-run as an option. That wait, you can check in the output whether you're seeing what you expect to see.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you say is done in the tests of toolchain.py. There I check against xFLAGS.

What I wanted to check here is simply that there aren't any issues when the input is correct. Arguably, it doesn't make too much sense, since the affected code is tested right below, calling options.postprocess(), where the code is really validated.

The reason I included these apparently useless tests is because the tests below check against just the postprocessing of options, instead of a "full" run. If you think this is not necessary I can simply delete these lines, but a more thorough test is already done in toolchain.py, I see no point in repeating it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing a 'full' run using toy-0.0.eb is meaningless imho, since toolchain.prepare will never be called since the dummy toolchain is used.

So, yes, I would drop this, since this test is mainly about how the optarch value is being parsed.

I would also change the tests where you check for the error on faulty output to not use eb_main but options.postprocess()?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, done.

Comment thread test/framework/toolchain.py Outdated
# if flag is '' there is not much to check. A more robust approach
# would be to check against a blacklist, but that is probably not necessary
else:
pass
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't do anything at all... :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not true. It is not completely useless, it can be used as a bad example ;-P. I'll make a blacklist check then.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Damian Alvarez added 2 commits January 25, 2017 12:29
Comment thread easybuild/tools/options.py Outdated
from easybuild.tools.package.utilities import avail_package_naming_schemes
from easybuild.tools.toolchain.compiler import DEFAULT_OPT_LEVEL, Compiler
from easybuild.tools.toolchain.compiler import DEFAULT_OPT_LEVEL, OPTARCH_SEP, \
OPTARCH_MAP_CHAR, Compiler
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please revert to a single line, sort alphabetically (constants first)

Comment thread easybuild/tools/options.py Outdated
n_compilers = len(optarch_parts)

# if the format is not adequate we refuse continuing
if n_compilers > 1 and n_compilers != self.options.optarch.count(OPTARCH_MAP_CHAR):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change this to:

if len(optarch_parts) > 1 and not all(OPTARCH_MAP_CHAR in p for p in optarch_parts):

which is a little bit stricter (it matter where the :s are, there should be one in each entry, otherwise "foo;bar::baz" is also valid)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and then you can drop n_compilers; not used anymore

Comment thread easybuild/tools/options.py Outdated
if n_compilers > 1 and n_compilers != self.options.optarch.count(OPTARCH_MAP_CHAR):
raise EasyBuildError("The optarch option has an incorrect syntax: %s" % self.options.optarch)
else:
logger = fancylogger.getLogger()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just use self.log rather than defining another logger

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable, but we should be consistent. There are more loggers being instantiated in that file.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a method (where self is an argument), you should use self.log.

In a function (no self in scope, like in process_software_build_specs), you may need to create a separate logger.

In this case, there's actually a global _log available, we should be using that consistently where a self.log is not available, e.g. in process_software_build_specs

Comment thread easybuild/tools/options.py Outdated
optarch_dict = {}
for compiler, compiler_opt in [x.split(OPTARCH_MAP_CHAR) for x in optarch_parts]:
if compiler in optarch_dict:
raise EasyBuildError("The optarch option contains duplicated entries for compiler %s: %s"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use raise EasyBuildError("...", foo, bar) (rather than "..." % (foo, bar))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above. I am ok with the change, but we should be consistent (to be done in a different PR?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, let's not clutter this PR with pure style changes in code we're not touching.

Feel free to tackle this in a separate PR, if you're up for it.

Comment thread easybuild/tools/options.py Outdated
else:
optarch_dict[compiler] = compiler_opt
self.options.optarch = optarch_dict
logger.info("Transforming optarch into a dict: %s" % self.options.optarch)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace "..." % with "...",

Comment thread test/framework/toolchain.py Outdated
gcc_options[1][1],
'xHost', # default optimal for Intel
'march=native', # default optimal for GCC
]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please fix indent + left-align ]

Comment thread test/framework/toolchain.py Outdated
intel_options[0][1],
intel_options[1][1],
gcc_options[0][1],
gcc_options[1][1],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, would it be clearer to just hardcode those strings here?

hardcoding is fine in tests if it makes things clearer

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think hardcoding will create more troubles down the road. If somebody needs to update this test they'll have to update that in various places........ which is plain annoying. Sure, that will be more readable, but less maintainable, and the only time somebody will read this test is to maintain it, so........ I vote for leaving it as it is ;-P

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I consider maintainability of tests less of an issue...

If you changes the values in intel_options and gcc_options, it's good to know where those are (also) used. Hardcoding the strings here forces you to also make the corresponding changes here, and makes you think about whether making the changes actually makes sense.

But fine, I don't feel too strongly about it, we're less strict in the tests.

Comment thread test/framework/toolchain.py Outdated
# Check that the correct flags are there
if enable and flag != '':
self.assertTrue(flag in flags, "optarch: True means '%s' in '%s'"
% (flag, flags))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer:

error_msg = "optarch: True means '%s' in '%s'" % (flags, optarch_flags)
self.assertTrue(flags in optarch_flags, error_msg)

Comment thread test/framework/toolchain.py Outdated
else:
for blacklisted_flag in blacklist:
self.assertFalse(blacklisted_flag in flags, "optarch: False means no '%s' in '%s'"
% (blacklisted_flag, flags))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here:

error_msg = "optarch: False means no '%s' in '%s'" % (blacklisted_flag, flags)
self.assertFalse(blacklisted_flag in optarch_flags, error_msg)

Comment thread test/framework/toolchain.py Outdated
self.assertTrue(flag in flags, "optarch: True means '%s' in '%s'"
% (flag, flags))

# Check that there aren't unexpected flags
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aren't any

Copy link
Copy Markdown
Member Author

@damianam damianam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@boegel I applied all your suggested changes, unless said otherwise in the individual comments.

Comment thread easybuild/tools/options.py Outdated
optarch_parts = self.options.optarch.split(OPTARCH_SEP)

# we expect to find a ':' in every entry in optarch, in case optarch is specified on a per-compiler basis
if len(optarch_parts) > 1 and not all(OPTARCH_MAP_CHAR in p for p in optarch_parts):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@damianam I gave this some more thought, and I actually think we need a more complex check to really validate this... We should check that there exactly one OPTARCH_MAP_CHAR in each part, now we may have multiple, which will screw things up later where we use .split(OPTARCH_MAP_CHAR)...

Comment thread easybuild/tools/options.py Outdated
for compiler, compiler_opt in [x.split(OPTARCH_MAP_CHAR) for x in optarch_parts]:
if compiler in optarch_dict:
raise EasyBuildError("The optarch option contains duplicated entries for compiler %s: %s",
compiler, self.options.optarch)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@damianam minor style issue: please indent further to align the c with the first " above...

Comment thread easybuild/tools/toolchain/compiler.py Outdated
self.log.info("_set_optimal_architecture: no optarch found for compiler %s. Ignoring option.",
current_compiler)
else:
raise EasyBuildError("optarch is neither an string or a dict. This should never ever happen")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@damianam we should show what's in optarch to make the error clearer? (although it's unlikely this will ever happen)

Comment thread easybuild/tools/options.py Outdated

# we expect to find a ':' in every entry in optarch, in case optarch is specified on a per-compiler basis
if (len(optarch_parts) > 1 and not all(p.count(OPTARCH_MAP_CHAR) == 1 for p in optarch_parts)) or \
(len(optarch_parts) == 1 and optarch_parts[0].count(OPTARCH_MAP_CHAR) > 1):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait, this is just

if not all(p.count(OPTARCH_MAP_CHAR) == 1 for p in optarch_parts)):

is it not?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. If you pass a simple string you'll raise and exception and break backwards compatibility, and force everyone to specify compiler. The count of OPTARCH_MAP_CHAR can be 0 if no compiler is specified

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I missed that bit, but this is very long and messy...

How about:

n_parts = len(optarch_parts)
map_char_cnts = [p.count(OPTARCH_MAP_CHAR) for p in optarch_parts]
if (n_parts > 1 and any(c != 1 for c in map_char_cnts)) or (n_parts == 1 and map_char_cnts[0] > 1):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks more messy to me..... The if is shorter, sure, but everything else seems unnecessarly complicated to me. But I don't really care....

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the variable names are key in my approach, maybe they could be improved.

In your version there's a lot of copy-pasting of code snippets, which makes it hard to parse imho...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very well then. Done

@boegel
Copy link
Copy Markdown
Member

boegel commented Jan 27, 2017

Going in, thanks @damianam!

Please also look into a documentation update @ http://easybuild.readthedocs.io/en/latest/Controlling_compiler_optimization_flags.html

@ocaisa
Copy link
Copy Markdown
Member

ocaisa commented Jan 27, 2017

This has got to be the longest conversation for a PR EasyBuild has ever had?

@damianam
Copy link
Copy Markdown
Member Author

We are social people, we like to talk. That has to be reason, right?

@boegel boegel merged commit c8e3542 into easybuilders:develop Jan 27, 2017
@damianam damianam deleted the optarch branch July 12, 2017 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants