Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[runtime] Optionally preload all the assemblies from the apk #2724

Merged
merged 2 commits into from
Feb 12, 2019

Conversation

grendello
Copy link
Contributor

Startup performance commit dispensed with preloading of all assemblies from
the APK since it's no longer needed by Xamarin.Android, thus improving the
overall startup time. However, it turns out that some applications (especially
those using Xamarin.Forms, but not only) rely on the fact that all the
assemblies are loaded by the time the application code is called. They do it by
calling AppDomain.CurrentDomain.GetAssemblies() and iterating over the
returned array in order to load resources, inject dependencies etc. This is not
a correct way to do it and nobody should rely on the GetAssemblies() call
returning all the assemblies available, but since this is currently required by
the aforementioned applications, we need to hurt performance and restore the
preload behavior albeit this time making it optional.

This commit implements the preload in a similar way as before, however this time
it is controlled by MSBuild property $(AndroidEnablePreloadAssemblies)
(which currently defaults to true) which the developer can set in their
project file and thus control the runtime behavior.

The commit also updates (courtesy of Jon Peppers @jonathanpeppers) the
Xamarin.Forms performance integration app in Xamarin.Android tests to fail in
case the assemblies are not preloaded. Currently, this application yields the
following performance hit on Pixel 3 XL with the preload enabled:

02-11 21:19:58.647 27410 27410 I monodroid-timing: Assembly load: FormsViewGroup.dll preloaded; elapsed: 0s:1::267136
02-11 21:19:58.648 27410 27410 I monodroid-timing: Assembly load: Newtonsoft.Json.dll preloaded; elapsed: 0s:0::999584
02-11 21:19:58.649 27410 27410 I monodroid-timing: Assembly load: Plugin.Connectivity.Abstractions.dll preloaded; elapsed: 0s:0::916459
02-11 21:19:58.650 27410 27410 I monodroid-timing: Assembly load: Plugin.Connectivity.dll preloaded; elapsed: 0s:0::886146
02-11 21:19:58.651 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Arch.Core.Common.dll preloaded; elapsed: 0s:0::890208
02-11 21:19:58.652 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Arch.Lifecycle.Common.dll preloaded; elapsed: 0s:0::918229
02-11 21:19:58.653 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Arch.Lifecycle.Runtime.dll preloaded; elapsed: 0s:0::880833
02-11 21:19:58.653 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Animated.Vector.Drawable.dll preloaded; elapsed: 0s:0::894323
02-11 21:19:58.654 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Annotations.dll preloaded; elapsed: 0s:0::928177
02-11 21:19:58.655 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Compat.dll preloaded; elapsed: 0s:0::941719
02-11 21:19:58.656 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Core.UI.dll preloaded; elapsed: 0s:0::936563
02-11 21:19:58.657 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Core.Utils.dll preloaded; elapsed: 0s:0::938594
02-11 21:19:58.658 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Design.dll preloaded; elapsed: 0s:0::983334
02-11 21:19:58.659 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Fragment.dll preloaded; elapsed: 0s:0::946042
02-11 21:19:58.660 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Media.Compat.dll preloaded; elapsed: 0s:0::927604
02-11 21:19:58.661 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Transition.dll preloaded; elapsed: 0s:0::959376
02-11 21:19:58.662 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v4.dll preloaded; elapsed: 0s:0::923750
02-11 21:19:58.663 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v7.AppCompat.dll preloaded; elapsed: 0s:1::82500
02-11 21:19:58.664 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v7.CardView.dll preloaded; elapsed: 0s:0::920313
02-11 21:19:58.665 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v7.MediaRouter.dll preloaded; elapsed: 0s:0::949323
02-11 21:19:58.666 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v7.Palette.dll preloaded; elapsed: 0s:0::906093
02-11 21:19:58.667 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v7.RecyclerView.dll preloaded; elapsed: 0s:0::900782
02-11 21:19:58.668 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Vector.Drawable.dll preloaded; elapsed: 0s:0::960416
02-11 21:19:58.669 27410 27410 I monodroid-timing: Assembly load: Xamarin.Forms.Core.dll preloaded; elapsed: 0s:0::972396
02-11 21:19:58.670 27410 27410 I monodroid-timing: Assembly load: Xamarin.Forms.Performance.Integration.dll preloaded; elapsed: 0s:0::898646
02-11 21:19:58.671 27410 27410 I monodroid-timing: Assembly load: Xamarin.Forms.Platform.Android.dll preloaded; elapsed: 0s:0::932813
02-11 21:19:58.672 27410 27410 I monodroid-timing: Assembly load: Xamarin.Forms.Platform.dll preloaded; elapsed: 0s:0::935677
02-11 21:19:58.673 27410 27410 I monodroid-timing: Assembly load: Xamarin.Forms.Xaml.dll preloaded; elapsed: 0s:0::928594
02-11 21:19:58.673 27410 27410 I monodroid-timing: Assembly load: preloaded 29 assemblies; elapsed: 0s:26::783805

@grendello grendello force-pushed the assembly-preload branch 2 times, most recently from ddbabb6 to 3d7983d Compare February 11, 2019 20:58
@jonpryor jonpryor added the full-mono-integration-build For PRs; run a full build (~6-10h for mono bumps), not the faster PR subset (~2h for mono bumps) label Feb 11, 2019
@jonpryor
Copy link
Member

build

2 similar comments
@grendello
Copy link
Contributor Author

build

@jonpryor
Copy link
Member

build

@jonpryor
Copy link
Member

It's very odd that the "build" comments aren't actually building the macOS PR Debug Build job.

I've managed to manually kick off a job.

We need the Debug results to demonstrate that the fix actually works, because the Release config will have [assembly: XamlCompilation (XamlCompilationOptions.Compile)] specified, which "solves" the problem without the runtime changes.

We thus need a Debug config build to demonstrate that the fix actually fixes things.

@jonpryor
Copy link
Member

build

@@ -2386,8 +2389,23 @@ because xbuild doesn't support framework reference assemblies.
</GetAddOnPlatformLibraries>
</Target>

<Target Name="_SetupAssemblyPreload"
Condition=" '$(AndroidEnablePreloadAssemblies)' == 'True' "
Inputs="$(IntermediateOutputPath)javastubs.cache"
Copy link
Member

Choose a reason for hiding this comment

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

I don't immediately understand why this would use $(IntermediateOutputPath)javastubs.cache as an input...though that is what _SetupEmbeddedDSOs does...

Copy link
Member

Choose a reason for hiding this comment

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

I don't think an appropriate Inputs/Outputs exists for this target, since there isn't a file that would influence what this does. I think we could just use <WriteLinesToFile WriteOnlyWhenDifferent="True"/> and not use any Inputs/Outputs. msbuild docs

_GenerateJavaStubs writes javastubs.cache looking at android:extractNativeLibs="false", which determines the value of $(_EmbeddedDSOsEnabled). So that makes sense for _SetupEmbeddedDSOs to use javastubs.cache as an input.

@@ -779,6 +779,11 @@ AndroidSystem::setup_environment (jstring_wrapper& name, jstring_wrapper& value)
knownEnvVars.MonoLLVM = true;
return;
}

if (strcmp (k, "mono.enable_assembly_preload") == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

...and regardless of what the default is, this should actually use the value specified. If someone does:

mono.enable_assembly_preload=0

i.e. explicitly disable it via a user-provided @(AndroidEnvironment) file, then it should be disabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

[Startup performance][0] commit dispensed with preloading of all assemblies from
the APK since it's no longer needed by Xamarin.Android, thus improving the
overall startup time. However, it turns out that some applications (especially
those using Xamarin.Forms, but not only) rely on the fact that all the
assemblies are loaded by the time the application code is called. They do it by
calling `AppDomain.CurrentDomain.GetAssemblies()` and iterating over the
returned array in order to load resources, inject dependencies etc. This is not
a correct way to do it and nobody should rely on the `GetAssemblies()` call
returning all the assemblies available, but since this is currently required by
the aforementioned applications, we need to hurt performance and restore the
preload behavior albeit this time making it optional.

This commit implements the preload in a similar way as before, however this time
it is controlled by MSBuild property `$(AndroidEnablePreloadAssemblies)`
(which currently defaults to `true`) which the developer can set in their
project file and thus control the runtime behavior.

The commit also updates (courtesy of Jon Peppers @jonathanpeppers) the
Xamarin.Forms performance integration app in Xamarin.Android tests to fail in
case the assemblies are *not* preloaded. Currently, this application yields the
following performance hit on Pixel 3 XL with the preload enabled:

    02-11 21:19:58.647 27410 27410 I monodroid-timing: Assembly load: FormsViewGroup.dll preloaded; elapsed: 0s:1::267136
    02-11 21:19:58.648 27410 27410 I monodroid-timing: Assembly load: Newtonsoft.Json.dll preloaded; elapsed: 0s:0::999584
    02-11 21:19:58.649 27410 27410 I monodroid-timing: Assembly load: Plugin.Connectivity.Abstractions.dll preloaded; elapsed: 0s:0::916459
    02-11 21:19:58.650 27410 27410 I monodroid-timing: Assembly load: Plugin.Connectivity.dll preloaded; elapsed: 0s:0::886146
    02-11 21:19:58.651 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Arch.Core.Common.dll preloaded; elapsed: 0s:0::890208
    02-11 21:19:58.652 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Arch.Lifecycle.Common.dll preloaded; elapsed: 0s:0::918229
    02-11 21:19:58.653 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Arch.Lifecycle.Runtime.dll preloaded; elapsed: 0s:0::880833
    02-11 21:19:58.653 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Animated.Vector.Drawable.dll preloaded; elapsed: 0s:0::894323
    02-11 21:19:58.654 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Annotations.dll preloaded; elapsed: 0s:0::928177
    02-11 21:19:58.655 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Compat.dll preloaded; elapsed: 0s:0::941719
    02-11 21:19:58.656 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Core.UI.dll preloaded; elapsed: 0s:0::936563
    02-11 21:19:58.657 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Core.Utils.dll preloaded; elapsed: 0s:0::938594
    02-11 21:19:58.658 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Design.dll preloaded; elapsed: 0s:0::983334
    02-11 21:19:58.659 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Fragment.dll preloaded; elapsed: 0s:0::946042
    02-11 21:19:58.660 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Media.Compat.dll preloaded; elapsed: 0s:0::927604
    02-11 21:19:58.661 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Transition.dll preloaded; elapsed: 0s:0::959376
    02-11 21:19:58.662 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v4.dll preloaded; elapsed: 0s:0::923750
    02-11 21:19:58.663 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v7.AppCompat.dll preloaded; elapsed: 0s:1::82500
    02-11 21:19:58.664 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v7.CardView.dll preloaded; elapsed: 0s:0::920313
    02-11 21:19:58.665 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v7.MediaRouter.dll preloaded; elapsed: 0s:0::949323
    02-11 21:19:58.666 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v7.Palette.dll preloaded; elapsed: 0s:0::906093
    02-11 21:19:58.667 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.v7.RecyclerView.dll preloaded; elapsed: 0s:0::900782
    02-11 21:19:58.668 27410 27410 I monodroid-timing: Assembly load: Xamarin.Android.Support.Vector.Drawable.dll preloaded; elapsed: 0s:0::960416
    02-11 21:19:58.669 27410 27410 I monodroid-timing: Assembly load: Xamarin.Forms.Core.dll preloaded; elapsed: 0s:0::972396
    02-11 21:19:58.670 27410 27410 I monodroid-timing: Assembly load: Xamarin.Forms.Performance.Integration.dll preloaded; elapsed: 0s:0::898646
    02-11 21:19:58.671 27410 27410 I monodroid-timing: Assembly load: Xamarin.Forms.Platform.Android.dll preloaded; elapsed: 0s:0::932813
    02-11 21:19:58.672 27410 27410 I monodroid-timing: Assembly load: Xamarin.Forms.Platform.dll preloaded; elapsed: 0s:0::935677
    02-11 21:19:58.673 27410 27410 I monodroid-timing: Assembly load: Xamarin.Forms.Xaml.dll preloaded; elapsed: 0s:0::928594
    02-11 21:19:58.673 27410 27410 I monodroid-timing: Assembly load: preloaded 29 assemblies; elapsed: 0s:26::783805

[0]: b90d3ab
static void
monodroid_Mono_UnhandledException_internal (MonoException *ex)
{
// Do nothing with it here, we let the exception naturally propagate on the managed side
}

static MonoDomain*
create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jobjectArray assemblies, jobject loader, bool is_root_domain)
create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jstring_array_wrapper &assemblies, jobject loader, bool is_root_domain)
Copy link
Contributor

Choose a reason for hiding this comment

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

@grendello I take it this means we can't get rid of the list of assemblies being passed in from the Java side now?

Copy link
Member

Choose a reason for hiding this comment

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

@dellis1972: that's exactly what this means

Copy link
Contributor Author

Choose a reason for hiding this comment

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

alas

@jonathanpeppers
Copy link
Member

We can ignore the Windows test failure:

BuildAppWithManagedResourceParserAndLibraries 

DesignTime build should be less than 10000 milliseconds.
Expected: less than or equal to 10000.0d
But was: 11990.0d

I have not seen this happen that often since adding [NonParallelizable] on this test, but I don't see how the changes here would cause it.

@jonpryor jonpryor merged commit e0da1f1 into master Feb 12, 2019
jonpryor pushed a commit that referenced this pull request Feb 12, 2019
Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/789540

Context: xamarin/Xamarin.Forms#5164

Commit b90d3ab altered how assemblies were loaded during process
startup in an effort to reduce time spent in `Runtime.init()`:
assembly loading has unavoidable overhead.  Reducing the number of
assemblies loaded results in less time spent in `Runtime.init()`.

This change also meant that the semantics of
`System.AppDomain.GetAssemblies()` changed: prior to b90d3ab,
`AppDomain.CurrentDomain.GetAssemblies()` would return *all*
assemblies present within the `.apk`.  Starting with b90d3ab,
`AppDomain.CurrentDomain.GetAssemblies()` would *instead* return only
the assemblies which had been loaded *up to that point in time*,
which may not be "everything".

We honestly didn't spend too much time thinking about this particular
change.  The b90d3ab behavior is what desktop .NET has done since
the beginning -- which makes sense, as desktop .NET didn't have a
concept of "application" for which one could ask for "all assemblies
that make up the application" -- so why would this break anything?
Anything broken in such an environment would fail on .NET.

Turns Out™ that there are mobile frameworks which assume that
`AppDomain.CurrentDomain.GetAssemblies()` will return all known
assemblies, and thus *break* with b90d3ab, including:

  * [Xamarin.Forms][0]
  * [MvvmCross][1]

Which brings us to [Xamarin.Forms Issue #5164][2], in which a
Xamarin.Forms app which uses CSS crashes with an NRE:

	UNHANDLED EXCEPTION:
	System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.NullReferenceException: Object reference not set to an instance of an object.
	  at Xamarin.Forms.Xaml.ApplyPropertiesVisitor.ProvideValue (System.Object& value, Xamarin.Forms.Xaml.ElementNode node, System.Object source, Xamarin.Forms.Xaml.XmlName propertyName) [0x000a7] in D:\a\1\s\Xamarin.Forms.Xaml\ApplyPropertiesVisitor.cs:243
	  at Xamarin.Forms.Xaml.ApplyPropertiesVisitor.Visit (Xamarin.Forms.Xaml.ElementNode node, Xamarin.Forms.Xaml.INode parentNode) [0x000c9] in D:\a\1\s\Xamarin.Forms.Xaml\ApplyPropertiesVisitor.cs:107
	  at Xamarin.Forms.Xaml.ElementNode.Accept (Xamarin.Forms.Xaml.IXamlNodeVisitor visitor, Xamarin.Forms.Xaml.INode parentNode) [0x000ac] in D:\a\1\s\Xamarin.Forms.Xaml\XamlNode.cs:149
	  at Xamarin.Forms.Xaml.RootNode.Accept (Xamarin.Forms.Xaml.IXamlNodeVisitor visitor, Xamarin.Forms.Xaml.INode parentNode) [0x00044] in D:\a\1\s\Xamarin.Forms.Xaml\XamlNode.cs:200
	  at Xamarin.Forms.Xaml.XamlLoader.Visit (Xamarin.Forms.Xaml.RootNode rootnode, Xamarin.Forms.Xaml.HydrationContext visitorContext, System.Boolean useDesignProperties) [0x0008b] in D:\a\1\s\Xamarin.Forms.Xaml\XamlLoader.cs:150
	  at Xamarin.Forms.Xaml.XamlLoader.Load (System.Object view, System.String xaml, System.Boolean useDesignProperties) [0x00058] in D:\a\1\s\Xamarin.Forms.Xaml\XamlLoader.cs:91
	  at Xamarin.Forms.Xaml.XamlLoader.Load (System.Object view, System.Type callingType) [0x00028] in D:\a\1\s\Xamarin.Forms.Xaml\XamlLoader.cs:69
	  at Xamarin.Forms.Xaml.Extensions.LoadFromXaml[TXaml] (TXaml view, System.Type callingType) [0x00000] in D:\a\1\s\Xamarin.Forms.Xaml\ViewExtensions.cs:36
	  at TheLittleThingsPlayground.Views.ThreeFivePage.InitializeComponent () [0x00001] in H:\github\TheLittleThingsPlayground\TheLittleThingsPlayground\obj\Debug
	g.cs:21
	  at TheLittleThingsPlayground.Views.ThreeFivePage..ctor () [0x00008] in H:\github\TheLittleThingsPlayground\TheLittleThingsPlayground\Views\ThreeFivePage.xaml.cs:12
	  at (wrapper managed-to-native) System.Reflection.MonoCMethod.InternalInvoke(System.Reflection.MonoCMethod,object,object[],System.Exception&)
	  at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters, System.Boolean wrapExceptions) [0x00005] in <84c6975c2cbc47b489a2a76477d7a312>:0

The `Xamarin.Forms.Xaml.dll` assembly isn't loaded when the
`ThreeFivePage` type is constructed, which eventually means that a
call to `DependencyService.Get<IResourcesLoader>()` returns `null`,
because `Xamarin.Forms.Xaml.ResourcesLoader, Xamarin.Forms.Xaml` was
not found or registered as an implementation for `IResourcesLoader`.

There is a *workaround* for a post-b90d3ab7 environment: explicitly
load the required assembly/assemblies earlier in the process:

	// In your MainActivity.cs
	protected override void OnCreate(Bundle bundle)
	{
	  // Workaround
	  Assembly.Load("Xamarin.Forms.Xaml");

	  base.OnCreate(bundle);

	  global::Xamarin.Forms.Forms.Init(this, bundle);
	  LoadApplication(new App());
	}

However, while there may be a workaround, this semantic change has
unknown impact on the overall ecosystem and environment.

Apps WILL break because of this change.

We could revert the assembly loading portion of b90d3ab, but that
means *all* apps need to pay the cost of loading all assemblies, even
if they don't need to pay that startup cost, a cost of ~30ms for
the `tests/Xamarin.Forms-Performance-Integration` app.

Instead, we'll enable the new functionality to be "opt-in", and make
the previous behavior the default.

Add a new `$(AndroidEnablePreloadAssemblies)` MSBuild property which,
when True, restores the previous behavior of loading all assemblies
within the `.apk` during process startup.  The default value is True.

If the `debug.mono.log` system property is set and contains `timing`,
`adb logcat` will contain messages regarding how much time was spent
loading each assembly, and the sum total for all assemblies:

	monodroid-timing: Assembly load: FormsViewGroup.dll preloaded; elapsed: 0s:1::267136
	monodroid-timing: Assembly load: Newtonsoft.Json.dll preloaded; elapsed: 0s:0::999584
	...
	monodroid-timing: Finished loading assemblies: preloaded 29 assemblies; elapsed: 0s:26::783805

Finally, update `tests/Xamarin.Forms-Performance-Integration` so that
it uses CSS, and thus hits the Xamarin.Forms codepath that triggers
the above `NullReferenceException.  This should validate that the fix
is correct, and help prevent future regressions.

[0]: https://github.com/xamarin/Xamarin.Forms/blob/cd1cf19fcad6ae049e90fdc5819c4f7c6adb57f6/Xamarin.Forms.Platform.Android/Forms.cs#L417
[1]: https://github.com/MvvmCross/MvvmCross/blob/43c1c7848d482adbc2bdc984746f36d74f64c190/MvvmCross/Core/MvxSetup.cs#L34
[2]: xamarin/Xamarin.Forms#5164
@grendello grendello deleted the assembly-preload branch February 12, 2019 18:23
@github-actions github-actions bot locked and limited conversation to collaborators Feb 1, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
full-mono-integration-build For PRs; run a full build (~6-10h for mono bumps), not the faster PR subset (~2h for mono bumps)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants