Mix Shared and Static Libraries to Reduce Bloat?

Hi,

My repo is here: https://github.com/spinicist/QUIT. This builds a decent number of smallish command-line programs. With static linking, each one ends up ~10 megs in size. However, the majority of that size is ITK code to read/write images and is hence duplicated between files.

I’m at a stage where it would be useful to distribute pre-compiled binaries to collaborators, and hence it would be great to reduce the total download size. The obvious (but apparently wrong) way to do this would be to switch to building a shared library that contains all the IO code and distribute that with the binaries. I’m developing on a Mac but I need to support Linux as well.

I tried building ITK as static libraries, tried to link those into my shared library, and then link my executables against the shared library. This led to utterly bizarre behaviour from one of my custom filters where accessing each output would cause the filter to re-update itself unnecessarily, and from the output it looked like multiple instances of the filter came into existence. When I tried to add some DisconnectPipeline() statements I had segfaults. None of this happens with static linking.

With some Googling, it seems that what I wanted to achieve is not straightforward (e.g. https://stackoverflow.com/questions/31503597/cmake-link-a-shared-library-to-static-libraries). I think my case is even more complicated because I do not want to pull the entirety of ITK into a shared library - only the IO parts - and then some executables also need to be statically linked against parts of ITK that are not in the library. I’m not sure it is worth trying to create a minimum working example of these issues because bluntly they were bizarre. If anyone wants to try and recreate them I can give instructions for what to do with the code from my repo.

I am going back to static linking before I lose my sanity. However I thought I would post this in case anyone has some suggestions for how to achieve what I want? I am hoping I am missing something obvious and over-complicating the situation. If there’s a completely different way to go about this, I’m all ears.

Thanks in advance.

The classic way to do this is to specify components when finding ITK, and only request the IOs you intend to use/allow. This cuts down the size of the compiled executables significantly, and a support for all ITK’s formats is rarely a must. Here is a handy example. To be used with static libraries.

1 Like

+1 to @dzenanz’s suggestion.

A helpful tool, written by @bill.lorensen for this task, is the WhatModulesITK.py script. Pass it your source code, and it spits out the CMake code with only the ITK modules needed.

1 Like

This is very cool! :metal:

This is a consequence of how static libraries work and how global variables are handled. For now, if mixing static libraries and shared libraries, link the static library into the executable / shared libraries only once. This is a general rule of thumb that is relevant to most static libraries. Linking the static library into multiple shared libraries or a shared library will cause issues. We are working a way mitigate these issues in ITK, but it would still require explicit action to call the relevant methods in applications that want to build this way.

@matt.mccormick - Thanks, I hadn’t thought of global variables. Finally I have a plausible explanation about the insanity I unleashed!

@dzenanz - Also thanks. I had looked into building ITK with only the IO options that I wanted, but got stuck because I couldn’t see which module enabled the core file formats (e.g. NIFTI and NRRD). However, with your example I see I was approaching this the wrong way round, as I only need to make sure the IOs I want are linked, they can all be built in the first place. I will look into this now!

Now if only I’d asked this question at the start of the week, I might have saved myself a good deal of head-scratching :slight_smile:

You can’t know in advance which question someone will find easy, and which one hard :smiley:

Thanks so much - trimming the IO down to NIFTI and NRRD cuts the final package size down from 150 megs to only 46! That’s much more reasonable.

2 Likes

As the final icing on the cake - can I disable modules such as ITKPNG from being built in the first place? This is purely to speed up my Travis builds. If I switch off ITK_BUILD_DEFAULT_MODULES in ccmake, then I don’t see any likely looking options. Similarly, is it possible to only build the itkLabelMapFilter out of ITKReview? That’s the only thing I need from review, but there is a lot of other stuff in there.

When you disable ITK_BUILD_DEFAULT_MODULES, then you individually enable all the modules which have not already been enabled by a dependency. When you configure ITK, look for a line like Enabled ITKPNG, requested by ITK_BUILD_DEFAULT_MODULES. too see which dependency is enabling it.

Module ITKReview has been part of ITK for so long that most, if not all its contents, should be moved into “normal” modules. This is a gradual process, done as the need arises. If you submit a patch which moves some class or a set of classes into normal module hierarchy, I don’t see obstructions for that patch to be accepted relatively fast.

1 Like

Thanks. I looked more closely at my Travis logs - a lot of stuff is required by ITKReview, including ITKIOPNG, which in turn requires ITKPNG.

I’ll add making a patch to move LabelMap out of the review to my todo list.

1 Like

What filter do you mean by LabelMap filter?

If you are referencing to the itkLabelGeometryImageFilter, that filter should be removed, deprecated, or placed into a remote module IMHO. It has numerous computation errors, and I have run into cases where it is extremely inefficient. Additionally, all functionality is correctly and efficient implemented with the LabelMapStatisticsImageFilter and ShapeLabelMapImageFilter. These filters also now have robust testing for cases of non-unit spacing, and non-axis aligned direction cosine matrix.

1 Like

@blowekamp - Thanks. I was confused. A long time ago, I was using LabelGeometryImageFilter, and hence needed itkReview. However, I’m not using it anymore but forgot that this meant I no longer needed itkReview. I’ve now successfully trimmed down the modules I want to build, which has halved the number of files Travis has to build in ITK from 2000 to 1000.

One interesting point - if I understood all my log messages correctly then after I disabled itkReview, a lot of modules were still required by itkTestKernel, which is part of the Core group, even though I have testing switched off as well (I’m not running the ITK tests in my Travis runs). This was counter-intuitive to me, and meant that I had to turn the Core group off before I could properly trim the build size.

1 Like