CartoDB / mobile-sdk

CARTO Mobile SDK core project
https://carto.com/docs/carto-engine/mobile-sdk/
BSD 3-Clause "New" or "Revised" License
186 stars 68 forks source link

Slow native library loading on android #319

Closed farfromrefug closed 4 years ago

farfromrefug commented 5 years ago

@mtehver Sorry to keep coming back on this issue. But it is kind of big issue for me. Android Carto Apps takes around 5 to 10s to load the native library.

I think there are multiple issues in the way the native library is built on android:

farfromrefug commented 5 years ago

Ok so i found the reason for the issue and i know how to fix it. But i need your help @mtehver So the issue is that there are far too many exported symbols in the generated so lib. For example all std symbols are exported You can see it with nm as explained in the link i shared before. Now looking at Mapbox i found out how they handle that. They use a version script https://github.com/mapbox/mapbox-gl-native/blob/6f0cda8c8b8e7831f04ae5e00b13387107dcad9b/platform/android/config.cmake#L22 to define what symbols to export. I tried using one like this:

{
  local: *;
};

and i ended up with a 2Mo lib. However it was not working becuase their were no symbol exported :D So what we should do is generate a version script file based on the generated JNI C++ files. That's where you can help me. Do you see a way to get the list of generated JNI file, or even JNI symbols?

mtehver commented 5 years ago

Thanks for version script suggestion! I did not know about this option. I did some tests and managed to reduce size of the .so files by 5%. I need to do few more tests before I am ready to merge this change.

Regarding loading times, this option had very little effect. When I measured the time it takes to call/return from registerLicense I got 32ms-80ms in Android emulator and the loading time did not really depend whether I used reduced symbol table or not. Thus I am very curious how you measured 5-10s.

About statically loaded objects: it is true that these resources make SDK about 700KB larger than without them. But loading additional 700KB should take around 5ms on most devices with internal flash memory nowadays (and perhaps 100ms if you have a really old device with external sdcard). Note that these resources are loaded as blobs and processed/decompressed only when really used.

farfromrefug commented 5 years ago

@mtehver about the loading time. The issue is not with registerLicense but with loadLibrary. At first i thought it was registerLicense but it is not. I think it happens as soon as i instantiate a MapView which would load native library. Though thinking about it i am wondering how a static in Java is actually handled. I mean is it run when the app starts or where that java class is first accessed. Need to compile the SDK with more logs to see what happens.

Finally about the statistically loaded resources. My issue is not more about the size of the SDK than about the fact that some are loaded in memory while actually not being ever used. I think we could do something like statically loading on demand. I write the code in JS for being easily readable

static cartoStyleData;
static function getCartoStyle() {
   if (!cartoStyleData) {
       cartoStyleData = [.....]
   }
   return cartoStyleData;
}

It is pretty easy to still use xxd to put the data in a C++ template file https://wiki.python.org/moin/Templating

mtehver commented 5 years ago

@farfromrefug MapView.registerLicense implicitly loads the native library. When I measure the time for System.loadLibrary("carto_mobile_sdk") I get 10ms or less even after rebooting the emulator.

About resources, as I wrote resources are loaded 'on demand', there is no style or bitmap decoding done during .so loading. So basically it already works the way you described.

farfromrefug commented 5 years ago

@mtehver ok will run the time tests on my phone to see why it does that.

Now about resources loading, i dont read it the same way looking at the code. If i look at the code cartostyles_v1_zip is a statically defined constant in CartoStylesV1ZIP.h. So it is "loaded" in memory (not loaded as the resource zip being read and loaded) on so loading. Same thing for the bitmaps.

farfromrefug commented 5 years ago

@mtehver ran some tests:

So you were right the slowdown does not come from there! Sorry about that.

Now going further it seems to come to the VectorLayer creation. Seeig that the app starts faster with carto default style, it could come from the parsing of my style. I am investigating right now

farfromrefug commented 5 years ago

@mtehver added some timers to see what's taking the longest. The test is run on my device:

One issue that i see too is that MBVectorTileDecoder::updateCurrentStyleSet is called twice in a row in my case. Was able to fix this. So that almost 2s won :D. Now about that it still seems really long 2s. What i see is that it really depends on the style. With yours it took 1162.53 ms which is already a lot better. Any idea what rules or what might be so long during style parsing? I don't have any image or svg in my style

About the license update duration. Could we have 2 listeners, one triggered when the license is verified "offline". There we can start drawing the map. The other one when the license is verified online. That way we wont have to wait for the online check. And you could still update the watermark once the online one is checked.

EDIT: actually found something weird with the license, might be on my side. It seems you are already doing what i am asking for. registerLicense returns the offline check and does the online check in a thread. What's weird is that when runing registerLicense in a thread it was actually waiting for the online check! Thus the very long time on my side. Will check that on my side

farfromrefug commented 5 years ago

@mtehver did more heavy logging/ testing. I know end up with slow down on style loading. Same thing happen with the sample android app. So a map style takes around 2s to load here on my device. It seems that most of the time is spend in CartoCSSMapLoader::buildMap And especially the compilation of layers here For example my poi layer takes 376ms to load

#poi {
    [subclass='alpine_hut'][rank<=10],
    [class=campsite][rank<=10],
    [zoom=14][rank<=1][class!='information'][class!='toilets'][class!='bus'][subclass!='tram_stop'][subclass!='station'][class!='picnic_site'],
    [zoom=15][rank<=2][class!='toilets'][class!='information'][class!='bus'][subclass!='tram_stop'][subclass!='station'],
    [zoom=16][rank<=10],
    [zoom=17][rank<=50],
    [zoom>=15][class=park][rank<=10],
    [zoom>=16][class=park][rank<=20],
    [zoom>=17][class=park][rank<=30],
    [zoom>=18] {

        ::icon[class!=null] {
            text-placement: [nuti::markers3d];
            text-name: @maki_icon;
            text-size: linear([view::zoom], (14, 10), (18, 10), (20, 14.0)) - 0.000001 * [rank];
            text-face-name: @maki;
            // text-opacity:0.8;
            text-feature-id: [name];

            text-halo-fill: @peak_halo;
            text-halo-rasterizer: fast;
            text-halo-radius: 1;
            text-fill: #495063;
            [class='park'] {
                text-fill: #76BC54;
            }
            [subclass='alpine_hut'],[class='campsite'] {
                text-fill: #854d04;
            }
            [class='hospital'] {
                text-fill: #4AA0E7;
            }
            [class='information'] {
                text-fill: #F3C600;
            }
            [class='bakery'], [class='restaurant'] {
                text-fill: #EF8000;
            }
        }
        ::label[name!=null] {
            text-name: @name;
            text-face-name: @mont;
            text-placement: [nuti::markers3d];
            text-line-spacing: -1;
            text-wrap-before: true;
            text-avoid-edges: true;
            text-fill: @poi_dark;
            text-size: linear([view::zoom], (14, 7), (18, 7), (20, 10.0)) - 0.000001 * [rank];
            text-wrap-width: step([zoom], (15, 80), (16, 90), (18, 100));
            text-feature-id: [name];
            [class!=null] { text-dy: 10; }
            text-halo-fill: @peak_halo;
            text-halo-rasterizer: fast;
            text-halo-radius: 1;
        }
    }
}

If i remove the rules on zoom that time is divided by 2. Which could mean the more rules the slower :s

I will try to get more into it to help you. But to me there is something to be improved here. Like a lot i think. I might try to create a simple project to load a style so that it would get easier to test on desktop

mtehver commented 5 years ago

@farfromrefug First about registerLicense measurement - you seem to measure full license checking time, though that should be quite irrelevant as the networking code is executed in background thread and it does not block main thread.

About debugging your style - I have added a small utility called 'css2xml' to the mobile-carto-libs repository. Due to dependencies, it is probably difficult to compile from the source and thus I have precompiled it for windows: https://drive.google.com/open?id=13giu6SHiknI45xAmjttWuLcNTd25GOq3

This utility can be used both for debugging and for optimizing style parsing time. Basically it takes json project file referencing to CartoCSS files/layers and produces self-contained extended Mapnik XML file. This .xml can be zipped together with assets (bitmaps and fonts) and SDK is able to automatically detect it and use it.

For debugging, I suggest looking into the XML and experimenting with different layers. I suspect you have complex rules that produce combinatorial explosion in the compiled code. Our built-in styles generate 300KB XML files when compiled, thus if your style is more that 1MB, that could explain the slow loading time.

farfromrefug commented 5 years ago

@mtehver wow that s really cool! So first i managed to built it on macOS. Fairly easy changes in the CMakeFiles.txt

Really easy :D Now i compile my style. A few things happen:

But now here comes the good part. If i load that XML style instead of the JSOn version, the style loading is more than 2 times faster!!!!!!! So this is huge already. Now i will try to look at the XML and see where i have too many rules. About the result of this. You don't publish your style as XML because of the shared data between the variants? Cause the increase in loading time is really nice! Thanks again. Will report here if i find something wrong to be looked at

farfromrefug commented 5 years ago

@mtehver got my style to drop to 300kb as XML. And in the same time got my JSON corresponding style to load in half the time it did before!

So here is the reason. in #transportation i was having at the same level some class rules and some subclass rules. this created a crazy amount of rules. The solution was to always put the subclass filters inside a class filter. I hope you understand what i mean ;)

mtehver commented 5 years ago

@farfromrefug Great to hear that you managed to optimized your style. Complex rules may create exponential number of rules wrt the number of conditions/filters.

I think the style loading time in SDK is acceptable, thus we keep the higher-level representation of the style currently. It also saves some space but that is not a primary concern.