2017-06-17 2 views
1

Ich schreibe eine Xamarin.Android App, die Syntaxhervorhebung für Code bietet. Es erstickt an großen Dateien. Ich debuggte meine App und stoppte sie zufällig, während sie eine verarbeitete. Zu meiner Überraschung hörte ich auf dieser Strecke fast die ganze Zeit:Wie zur Umgehung der Langsamkeit von Xamarin.Android Java Interop

var span = new ForegroundColorSpan(color); 

Dies ist das einzige Stück von Java-Code, den ich sehr häufig anrufen, während ich bin Syntax-Hervorhebung. This ist die Implementierung der Methode, die ein One-Liner ist, der einem Feld int zuweist. Offensichtlich muss Xamarins Java-Interop der Schuldige sein.

Ich bin mir nicht sicher, wie ich das umgehen kann. Ich versuchte, die ForegroundColorSpan für eine gegebene unter Verwendung Dictionary zwischenzuspeichern, aber wegen der Art, wie die spannable APIs entworfen werden, können Sie SpannableString.setSpanwith the same span instance für verschiedene Bereiche nicht benennen. Habe ich andere Optionen hier?

bearbeiten: Sorry, Interop, nicht Marshalling. Ich habe den Titel geändert.

bearbeiten 2: OK, ich habe einen viel tieferen Einblick in dieses Problem genommen. Zuerst habe ich eine minimale Repro:

var list = new List<ForegroundColorSpan>(); 
for (int i = 0; i < 100000; i++) 
{ 
    list.Add(new ForegroundColorSpan(Color.Blue)); 
} 

Versuchen Sie, dies in einer leeren Xamarin App laufen und es wird ewig dauern. Das wirklich, wirklich seltsame daran ist jedoch, dass die Zeit nicht proportional zur Anzahl der Iterationen ist. Versuchen Sie, die Anzahl der Iterationen auf 40000 festzulegen, und es wird in einer Sekunde abgeschlossen. Versuchen Sie es auf 50000 zu setzen, und es wird ewig dauern. Wenn Sie den Debugger nach dem Zufallsprinzip pausieren und die i überprüfen, werden Sie sehen, dass es immer um 45/46K ist. Es scheint einen magischen Grenzpunkt bei etwa 45K Elementen zu geben, wo die Schleife plötzlich unbrauchbar langsam wird - und damit meine ich, dass 5 neue Objekte erzeugt werden pro Sekunde.

Zweitens sah ich in dem, was der Konstruktor der Haube tut unter durch das Working with JNI Dokument zu lesen, speziell this part, und ich schrieb manuell Bindungen für ForegroundColorSpan so konnte ich sie debuggen:

[Register("android/text/style/ForegroundColorSpan", DoNotGenerateAcw = true)] 
internal class FastForegroundColorSpan : JavaObject 
{ 
    private static readonly IntPtr class_ref = JNIEnv.FindClass("android/text/style/ForegroundColorSpan"); 

    private static IntPtr id_ctor_I; 

    [Register(".ctor", "(I)V", "")] 
    public unsafe FastForegroundColorSpan(int color) 
     : base(IntPtr.Zero, JniHandleOwnership.DoNotTransfer) 
    { 
     if (Handle != IntPtr.Zero) 
     { 
      return; 
     } 

     if (id_ctor_I == IntPtr.Zero) 
     { 
      id_ctor_I = JNIEnv.GetMethodID(class_ref, "<init>", "(I)V"); 
     } 

     var args = stackalloc JValue[1]; 
     args[0] = new JValue(color); 
     var handle = JNIEnv.NewObject(class_ref, id_ctor_I, args); 
     SetHandle(handle, JniHandleOwnership.TransferLocalRef); 
    } 
} 

ich meinen Code geschaltet Verwenden Sie new FastForegroundColorSpan anstelle von new ForegroundColorSpan. Wenn ich den Code im Debugger jetzt zufällig stoppe, bricht er immer um SetHandle. Ich schaute in die source code of SetHandle, und tatsächlich dort ist eine Menge Sachen, die dort gehen, einschließlich vieler Interopaufrufe zu JNIEnv und locking/schwache Hinweise. Was ich aber immer noch nicht erklären kann ist, warum die App an einem magischen Grenzpunkt nach ~ 45K Spannweiten immer langsamer (und so dramatisch) wird.

+0

Wäre schön, wenn Sie ein wenig Code teilen könnten, der das Problem repliziert hat. – jzeferino

+0

@jzeferino Ich tat, es ist der One-Liner-Ausschnitt. Wenn Sie suchen, wie ich es in meiner App verwende, finden Sie das [hier] (https://github.com/jamesqo/Repository/blob/wip-spannabletext/Repository/EditorServices/Highlighting/SyntaxColorer.cs#L33) , aber wirklich alles, was Sie tun müssen, ist eine For-Schleife zu schreiben, die oft läuft und 'ForegroundColorSpan()' aufruft. –

Antwort

1

Das wirklich, wirklich seltsame daran ist jedoch, dass die Zeit, die es dauert, nicht proportional zur Anzahl der Iterationen ist. Versuchen Sie, die Anzahl der Iterationen auf 40000 festzulegen, und zwar in Sekundenschnelle. Versuchen Sie es auf 50000 zu setzen, und es wird ewig dauern.

Das Problem ist, dass Sie zu viele Java.lang.object-Instanzen gleichzeitig haben.Wenn Sie Global Reference Message beziehen, können Sie die folgende Erklärung finden:

Leider Android Emulatoren erlauben nur 2000 globale Referenzen zu einem Zeitpunkt, zu existieren. Hardware hat eine viel höhere Grenze von 52000 globalen Referenzen. Die untere Grenze von kann problematisch sein, wenn Anwendungen auf dem Emulator ausgeführt werden. Daher kann es sehr nützlich sein, zu wissen, woher die Instanz kam.

Sie können den globalen Referenz loggig (GREF) Protokollierung aktivieren durch:

adb shell setprop debug.mono.log gref 

Auch gibt es eine similar case, die Sie sich beziehen können.

Also, für die Lösung müssen Sie diese Schleife löschen, und versuchen, dies entsprechend Ihrer Anforderung zu umgehen.

+0

Elvis, danke für die klare und gründliche Antwort. Ich bin angenehm überrascht, eine Antwort von jemandem bekommen zu haben, der dieses Thema genau kennt. Ich habe gestern damit angefangen, das Problem zu umgehen, indem ich eine Java-Bibliothek erstellt habe, in der ich alle Objekte instanziiert habe und diese einmal aus C# über eine Bindungsbibliothek aufgerufen habe. –

Verwandte Themen