Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit 0cb8e2d

Browse files
authored
[Xamarin.Android.Tools.ApiXmlAdjuster] Use Dictionaries for perf (#756)
The `Xamarin.Android.Tools.ApiXmlAdjuster` process builds an in-memory model of every Java type we know about, and then queries this model many times in order to ensure we can resolve every needed Java type to build a binding. The data structure of this model is: public class JavaApi { public List<JavaPackage> Packages { get; } } public class JavaPackage { public List<JavaType> Types { get; } } The model is then queried using LINQ:: // Bad var type = api.Packages.SelectMany (p => p.Types) .FirstOrDefault (t => t.Name == type_name); // Less bad var type = api.Packages.FirstOrDefault (p => p.Name == pkg_name) ?.Types.FirstOrDefault (t => t.Name == type_name); In the random GooglePlayServices package I used for testing, `JavaApi` contained 310 packages and a total of 5941 types. Repeatedly looping through them looking for the correct type takes a considerable amount of time. Instead, we can use a `Dictionary` to store packages and types keyed by name to significantly speed up type resolution: public class JavaApi { public Dictionary<string, JavaPackage> Packages { get; } } public class JavaPackage { public Dictionary<string, List<JavaType>> Types { get; } } For the GPS project, this reduced time taken considerably: | Version | Time | | ------- | ---- | | master | 9.3s | | This PR | 1.4s | The only "interesting" detail is that we can have multiple types with the same *Java* name, such as `MyInterface` and `MyInterfaceConsts`. Thus we need to use `Dictionary<string, List<JavaType>>` to ensure we collect them all.
1 parent 99f8990 commit 0cb8e2d

17 files changed

Lines changed: 150 additions & 92 deletions

File tree

‎src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/JavaStubParser.cs‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,12 @@ public JavaStubGrammar ()
315315
identifier.AstConfig.NodeCreator=(ctx,node)=>node.AstNode=node.Token.ValueString;
316316
compile_unit.AstConfig.NodeCreator=(ctx,node)=>{
317317
ProcessChildren(ctx,node);
318-
node.AstNode=newJavaPackage(null){Name=(string)node.ChildNodes[0].AstNode,Types=((IEnumerable<JavaType>)node.ChildNodes[2].AstNode).ToList()};
318+
varpkg=newJavaPackage(null){Name=(string)node.ChildNodes[0].AstNode};
319+
320+
foreach(vartin(IEnumerable<JavaType>)node.ChildNodes[2].AstNode)
321+
pkg.AddType(t);
322+
323+
node.AstNode=pkg;
319324
};
320325
opt_package_decl.AstConfig.NodeCreator=SelectSingleChild;
321326
package_decl.AstConfig.NodeCreator=SelectChildValueAt(1);
@@ -637,9 +642,13 @@ public JavaStubParser ()
637642
voidFlattenNestedTypes(JavaPackagepackage)
638643
{
639644
varresults=newList<JavaType>();
640-
foreach(vartinpackage.Types)
645+
foreach(vartinpackage.AllTypes)
641646
Flatten(results,t);
642-
package.Types=results.ToList();
647+
648+
package.ClearTypes();
649+
650+
foreach(vartinresults)
651+
package.AddType(t);
643652

644653
voidFlatten(List<JavaType>list,JavaTypet)
645654
{

‎src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApi.XmlModel.cs‎

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,77 @@ public partial class JavaApi
1010
{
1111
publicJavaApi()
1212
{
13-
Packages=newList<JavaPackage>();
13+
Packages=newDictionary<string,JavaPackage>();
1414
}
1515

1616
publicstring?ExtendedApiSource{get;set;}
1717
publicstring?Platform{get;set;}
18-
publicIList<JavaPackage>Packages{get;set;}
18+
publicIDictionary<string,JavaPackage>Packages{get;}
19+
20+
publicICollection<JavaPackage>AllPackages=>Packages.Values;
1921
}
2022

2123
publicpartialclassJavaPackage
2224
{
25+
privateDictionary<string,List<JavaType>>types=newDictionary<string,List<JavaType>>();
26+
2327
publicJavaPackage(JavaApi?parent)
2428
{
2529
Parent=parent;
26-
27-
Types=newList<JavaType>();
2830
}
2931

3032
publicJavaApi?Parent{get;privateset;}
3133

3234
publicstring?Name{get;set;}
3335
publicstring?JniName{get;set;}
34-
publicIList<JavaType>Types{get;set;}
35-
36+
37+
// Yes, there can be multiple types with the same *Java* name.
38+
// For example:
39+
// - MyInterface
40+
// - MyInterfaceConsts
41+
// It's debatable whether we handle this "properly", as most callers just
42+
// do `First ()`, but it's been working for years so I'm not changing it.
43+
// Exposes an IReadOnlyDictionary so caller cannot bypass our AddType/RemoveType code.
44+
publicIReadOnlyDictionary<string,List<JavaType>>Types=>types;
45+
46+
// Use this for a flat list of *all* types
47+
publicIEnumerable<JavaType>AllTypes=>Types.Values.SelectMany(v =>v);
48+
49+
publicvoidAddType(JavaTypetype)
50+
{
51+
// If this is a duplicate key, add it to existing list
52+
if(Types.TryGetValue(type.Name!,outvarlist)){
53+
list.Add(type);
54+
return;
55+
}
56+
57+
// Add to a new list
58+
varnew_list=newList<JavaType>();
59+
new_list.Add(type);
60+
61+
types.Add(type.Name!,new_list);
62+
}
63+
64+
publicvoidRemoveType(JavaTypetype)
65+
{
66+
if(!Types.TryGetValue(type.Name!,outvarlist))
67+
return;
68+
69+
// Remove 1 type from list if it contains multiple types
70+
if(list.Count>1){
71+
list.Remove(type);
72+
return;
73+
}
74+
75+
// Remove the whole dictionary entry
76+
types.Remove(type.Name!);
77+
}
78+
79+
publicvoidClearTypes()
80+
{
81+
types.Clear();
82+
}
83+
3684
// Content of this value is not stable.
3785
publicoverridestringToString()
3886
{
@@ -123,14 +171,14 @@ static ManagedType ()
123171
dummy_system_package=newJavaPackage(null){Name="System"};
124172
system_object=newManagedType(dummy_system_package){Name="Object"};
125173
system_exception=newManagedType(dummy_system_package){Name="Exception"};
126-
dummy_system_package.Types.Add(system_object);
127-
dummy_system_package.Types.Add(system_exception);
174+
dummy_system_package.AddType(system_object);
175+
dummy_system_package.AddType(system_exception);
128176
dummy_system_io_package=newJavaPackage(null){Name="System.IO"};
129177
system_io_stream=newManagedType(dummy_system_io_package){Name="Stream"};
130-
dummy_system_io_package.Types.Add(system_io_stream);
178+
dummy_system_io_package.AddType(system_io_stream);
131179
dummy_system_xml_package=newJavaPackage(null){Name="System.Xml"};
132180
system_xml_xmlreader=newManagedType(dummy_system_xml_package){Name="XmlReader"};
133-
dummy_system_io_package.Types.Add(system_xml_xmlreader);
181+
dummy_system_io_package.AddType(system_xml_xmlreader);
134182
}
135183

136184
publicstaticIEnumerable<JavaPackage>DummyManagedPackages{

‎src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiDefectFinderExtensions.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public static class JavaApiDefectFinderExtensions
77
{
88
publicstaticvoidFindDefects(thisJavaApiapi)
99
{
10-
foreach(vartypeinapi.Packages.SelectMany(p =>p.Types).Where(t =>!t.IsReferenceOnly))
10+
foreach(vartypeinapi.AllPackages.SelectMany(p =>p.AllTypes).Where(t =>!t.IsReferenceOnly))
1111
type.FindDefects();
1212
}
1313

‎src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiGenericInheritanceMapperExtensions.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public static class JavaApiGenericInheritanceMapperExtensions
88
{
99
publicstaticvoidCreateGenericInheritanceMapping(thisJavaApiapi)
1010
{
11-
foreach(varklsinapi.Packages.SelectMany(p =>p.Types).OfType<JavaClass>())
11+
foreach(varklsinapi.AllPackages.SelectMany(p =>p.AllTypes).OfType<JavaClass>())
1212
kls.PrepareGenericInheritanceMapping();
1313
}
1414

‎src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiNonBindableStripper.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public static class JavaApiNonBindableStripper
99
publicstaticvoidStripNonBindables(thisJavaApiapi)
1010
{
1111
varinvalids=newList<JavaMember>();
12-
foreach(varmemberinapi.Packages.SelectMany(p =>p.Types)
12+
foreach(varmemberinapi.AllPackages.SelectMany(p =>p.AllTypes)
1313
.SelectMany(t =>t.Members).Where(m =>m.Name!=null&&m.Name.Contains('$')))
1414
invalids.Add(member);
1515
foreach(varinvalidininvalids)

‎src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiOverrideMarkerExtensions.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public static void MarkOverrides (this JavaApi api)
1414

1515
publicstaticvoidMarkOverrides(thisJavaApiapi,HashSet<JavaClass>doneList)
1616
{
17-
foreach(varklsinapi.Packages.SelectMany(p =>p.Types).OfType<JavaClass>())
17+
foreach(varklsinapi.AllPackages.SelectMany(p =>p.AllTypes).OfType<JavaClass>())
1818
kls.MarkOverrides(doneList);
1919
}
2020

‎src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiTypeResolverExtensions.cs‎

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,31 @@ static JavaTypeReference JavaTypeNameToReference (this JavaApi api, JavaTypeName
3939

4040
publicstaticJavaTypeFindNonGenericType(thisJavaApiapi,string?name)
4141
{
42-
varret=FindPackages(api,name??"")
43-
.SelectMany(p =>p.Types)
44-
.FirstOrDefault(t =>name==(t.Parent?.Name!=null?t.Parent.Name+".":"")+t.Name);
45-
if(ret==null)
46-
ret=ManagedType.DummyManagedPackages
47-
.SelectMany(p =>p.Types)
48-
.FirstOrDefault(t =>t.FullName==name);
42+
// Given a type name like 'android.graphics.BitmapFactory.Options'
43+
// We're going to search for:
44+
// - Pkg: android.graphics.BitmapFactory Type: Options
45+
// - Pkg: android.graphics Type: BitmapFactory.Options
46+
// - Pkg: android Type: graphics.BitmapFactory.Options
47+
// etc. We will short-circuit as soon as we find a match
48+
varindex=name?.LastIndexOf('.')??-1;
49+
50+
while(index>0){
51+
varns=name!.Substring(0,index);
52+
vartype_name=name.Substring(index+1);
53+
54+
if(api.Packages.TryGetValue(ns,outvarpkg)){
55+
if(pkg.Types.TryGetValue(type_name,outvartype))
56+
returntype.First();
57+
}
58+
59+
index=name.LastIndexOf('.',index-1);
60+
}
61+
62+
// See if it's purely a C# type
63+
varret=ManagedType.DummyManagedPackages
64+
.SelectMany(p =>p.AllTypes)
65+
.FirstOrDefault(t =>t.FullName==name);
66+
4967
if(ret==null){
5068
// We moved this type to "mono.android.app.IntentService" which makes this
5169
// type resolution fail if a user tries to reference it in Java.
@@ -58,43 +76,26 @@ public static JavaType FindNonGenericType (this JavaApi api, string? name)
5876
returnret;
5977
}
6078

61-
staticIEnumerable<JavaPackage>FindPackages(JavaApiapi,stringname)
62-
{
63-
// Given a type name like "java.lang.Object", return packages that could
64-
// possibly contain the type so we don't search all packages, ie:
65-
// - java.lang
66-
// - java
67-
varpackage_names=newList<string>();
68-
intindex;
69-
70-
while((index=name.LastIndexOf('.'))>=0){
71-
name=name.Substring(0,index);
72-
package_names.Add(name);
73-
}
74-
75-
returnapi.Packages.Where(p =>package_names.Contains(p.Name,StringComparer.Ordinal)).ToList();
76-
}
77-
7879
publicstaticvoidResolve(thisJavaApiapi)
7980
{
8081
while(true){
8182
boolerrors=false;
82-
foreach(vartinapi.Packages.SelectMany(p =>p.Types).OfType<JavaClass>().ToArray())
83+
foreach(vartinapi.AllPackages.SelectMany(p =>p.AllTypes).OfType<JavaClass>().ToArray())
8384
try{
8485
t.Resolve();
8586
}
8687
catch(JavaTypeResolutionExceptionex){
8788
Log.LogError("Error while processing type '{0}': {1}",t,ex.Message);
8889
errors=true;
89-
t.Parent?.Types.Remove(t);
90+
t.Parent?.RemoveType(t);
9091
}
91-
foreach(vartinapi.Packages.SelectMany(p =>p.Types).OfType<JavaInterface>().ToArray())
92+
foreach(vartinapi.AllPackages.SelectMany(p =>p.AllTypes).OfType<JavaInterface>().ToArray())
9293
try{
9394
t.Resolve();
9495
}catch(JavaTypeResolutionExceptionex){
9596
Log.LogError("Error while processing type '{0}': {1}",t,ex.Message);
9697
errors=true;
97-
t.Parent?.Types.Remove(t);
98+
t.Parent?.RemoveType(t);
9899
}
99100
if(!errors)
100101
break;

‎src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiXmlGeneratorExtensions.cs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ public static void Save (this JavaApi api, XmlWriter writer)
2424
if(api.Platform!=null)
2525
writer.WriteAttributeString("platform",api.Platform);
2626

27-
foreach(varpkginapi.Packages){
28-
if(!pkg.Types.Any(t =>!t.IsReferenceOnly))
27+
foreach(varpkginapi.AllPackages){
28+
if(!pkg.AllTypes.Any(t =>!t.IsReferenceOnly))
2929
continue;
3030
writer.WriteStartElement("package");
3131
writer.WriteAttributeString("name",pkg.Name);
3232
if(!string.IsNullOrEmpty(pkg.JniName)){
3333
writer.WriteAttributeString("jni-name",pkg.JniName);
3434
}
35-
foreach(vartypeinpkg.Types){
35+
foreach(vartypeinpkg.AllTypes){
3636
if(type.IsReferenceOnly)
3737
continue;// skip reference only types
3838
if(typeisJavaClass)

‎src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaApiXmlLoaderExtensions.cs‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@ public static void Load (this JavaApi api, XmlReader reader, bool isReferenceOnl
3232
break;// </api>
3333
if(reader.NodeType!=XmlNodeType.Element||reader.LocalName!="package")
3434
throwXmlUtil.UnexpectedElementOrContent("api",reader,"package");
35-
varpkg=api.Packages.FirstOrDefault(p =>p.Name==reader.GetAttribute("name"));
36-
if(pkg==null){
35+
36+
varname=reader.GetAttribute("name");
37+
38+
if(!api.Packages.TryGetValue(name,outvarpkg)){
3739
pkg=newJavaPackage(api);
38-
api.Packages.Add(pkg);
40+
api.Packages.Add(name,pkg);
3941
}
42+
4043
pkg.Load(reader,isReferenceOnly);
4144
}while(true);
4245

@@ -67,11 +70,11 @@ public static void Load (this JavaPackage package, XmlReader reader, bool isRefe
6770
if(reader.LocalName=="class"){
6871
varkls=newJavaClass(package){IsReferenceOnly=isReferenceOnly};
6972
kls.Load(reader);
70-
package.Types.Add(kls);
73+
package.AddType(kls);
7174
}elseif(reader.LocalName=="interface"){
7275
variface=newJavaInterface(package){IsReferenceOnly=isReferenceOnly};
7376
iface.Load(reader);
74-
package.Types.Add(iface);
77+
package.AddType(iface);
7578
}else
7679
throwXmlUtil.UnexpectedElementOrContent("package",reader,"class","interface");
7780
}while(true);

‎tests/Java.Interop.Tools.JavaSource-Tests/JavaStubParserTests.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static void m (String text) {
3535
Assert.AreEqual(null,package.Name);
3636
Assert.AreEqual(1,package.Types.Count);
3737

38-
varExample_Type=package.Types[0];
38+
varExample_Type=package.AllTypes.First();
3939
Assert.AreEqual("Example",Example_Type.FullName);
4040

4141
Assert.AreEqual(1,Example_Type.Members.Count);

0 commit comments

Comments
 (0)