2013-07-07 6 views
9

Eksperymentuję z optymalizacją kombinatorów parsera w języku C#. Jedną z możliwych optymalizacji, gdy zserializowany format pasuje do formatu w pamięci, jest po prostu wykonanie (niebezpiecznej) notatki danych, które mają zostać przeanalizowane w instancji lub nawet wielu instancjach tego typu.Korzystanie z odbicia w celu określenia sposobu rozmieszczenia typu .Net w pamięci

Chcę napisać kod, który określa, czy format w pamięci jest zgodny z serializowanym formatem, aby dynamicznie określić, czy można zastosować optymalizację. (Oczywiście jest to niebezpieczna optymalizacja i może nie działać z wielu subtelnych powodów.) Po prostu eksperymentuję, nie planuję użyć tego w kodzie produkcyjnym.)

Używam atrybutu [StructLayout(LayoutKind.Sequential, Pack = 1)], aby wymusić brak wypełnienia i wymuszenie kolejności w pamięci zgodnie z kolejnością deklaracji. Sprawdzam ten atrybut za pomocą refleksji, ale tak naprawdę wszystko to potwierdza "brak dopełnienia". Potrzebuję również kolejności pól. (Zdecydowanie nie będę musiał ręcznie określać atrybutów FieldOffset dla każdego pola, ponieważ byłoby to bardzo podatne na błędy.)

Założono, że mogę użyć kolejności pól zwróconych przez GetFields, ale dokumentacja wyraźnie wzywa do kolejność jest nieokreślona.

Biorąc pod uwagę, że wymuszam kolejność pól za pomocą atrybutu StructLayout, czy istnieje sposób na zastanowienie się nad tym zamówieniem?

edycja Nie mam nic przeciwko ograniczeniu, że wszystkie pola muszą być blittable.

+0

Czy możesz tego nie zrozumieć, odzwierciedlając te atrybuty? –

+0

@newStackExchangeInstance Jakie atrybuty? –

+1

'LayoutKind.Sequential' kontroluje tylko zarządzaną reprezentację, jeśli w strukturze występują tylko typy blittable. Jeśli istnieje typ niemożliwy do zablokowania, kolejność pól jest w każdym razie kontrolowana przez środowisko wykonawcze. Na przykład. patrz http://stackoverflow.com/q/14024483/11683. – GSerg

Odpowiedz

5

To jest niepotrzebny w przypadku korzystania LayoutKind.Sequential z blittable typów

Nie trzeba użyć refleksji lub inny mechanizm, aby dowiedzieć się kolejność pól struct w pamięci, dopóki wszystkie pola są blittable .

Pola możliwe do wyłonienia dla struktury deklarowanej za pomocą LayoutKind.Sequential będą przechowywane w pamięci w kolejności, w której deklarowane są pola. Oto, co oznacza LayoutKind.Sequential!

From this documentation:

Dla blittable typów LayoutKind.Sequential kontroluje zarówno układ w zarządzanej pamięci i układu w pamięci niekontrolowana. W przypadku typów, których nie można rozcieńczyć, kontroluje układ, gdy klasa lub struktura jest przekazywana do niezarządzanego kodu, ale nie kontroluje układu w zarządzanej pamięci.

Należy pamiętać, że to nie powiedzieć, ile wyściółka każde pole jest używany. Aby się tego dowiedzieć, zobacz poniżej.

Aby ustalić kolejność pól przy użyciu LayoutKind.Auto lub przesunięcia pól przy użyciu dowolnego układu

Jest to dość łatwe do znalezienia przesunięcia pól struct jeśli jesteś zadowolony w użyciu niebezpieczny kod, a nie użyj refleksji.

Musisz tylko podać adres każdego pola struktury i obliczyć jego przesunięcie od początku struktury. Znając przesunięcia każdego pola, możesz obliczyć ich kolejność (oraz wszelkie bajty wypełnienia między nimi).Aby obliczyć bajty wypełnienia używane w ostatnim polu (jeśli jest), musisz również uzyskać całkowity rozmiar struktury, używając sizeof(StructType).

Poniższy przykład działa dla wersji 32-bitowej i 64-bitowej. Należy pamiętać, że nie trzeba używać fixed słowa kluczowego, ponieważ struktura jest już ustalona ze względu na to, że na stosie (otrzymasz błąd kompilacji, jeśli spróbujesz użyć fixed z nim):

using System; 
using System.Runtime.InteropServices; 

namespace Demo 
{ 
    [StructLayout(LayoutKind.Auto, Pack = 1)] 

    public struct TestStruct 
    { 
     public int I; 
     public double D; 
     public short S; 
     public byte B; 
     public long L; 
    } 

    class Program 
    { 
     void run() 
     { 
      var t = new TestStruct(); 

      unsafe 
      { 
       IntPtr p = new IntPtr(&t); 
       IntPtr pI = new IntPtr(&t.I); 
       IntPtr pD = new IntPtr(&t.D); 
       IntPtr pS = new IntPtr(&t.S); 
       IntPtr pB = new IntPtr(&t.B); 
       IntPtr pL = new IntPtr(&t.L); 

       Console.WriteLine("I offset = " + ptrDiff(p, pI)); 
       Console.WriteLine("D offset = " + ptrDiff(p, pD)); 
       Console.WriteLine("S offset = " + ptrDiff(p, pS)); 
       Console.WriteLine("B offset = " + ptrDiff(p, pB)); 
       Console.WriteLine("L offset = " + ptrDiff(p, pL)); 

       Console.WriteLine("Total struct size = " + sizeof(TestStruct)); 
      } 
     } 

     long ptrDiff(IntPtr p1, IntPtr p2) 
     { 
      return p2.ToInt64() - p1.ToInt64(); 
     } 

     static void Main() 
     { 
      new Program().run(); 
     } 
    } 
} 

w celu określenia przesunięcia pól przy użyciu LayoutKind.Sequential

Jeśli struct używa LayoutKind.Sequential następnie można użyć Marshal.OffsetOf() aby uzyskać przesunięcie bezpośrednio, ale to nie robi praca z LayoutKind.Auto:

foreach (var field in typeof(TestStruct).GetFields()) 
{ 
    var offset = Marshal.OffsetOf(typeof (TestStruct), field.Name); 
    Console.WriteLine("Offset of " + field.Name + " = " + offset); 
} 

Jest to zdecydowanie lepszy sposób, aby to zrobić, jeśli używasz LayoutKind.Sequential, ponieważ nie wymaga kodu unsafe, a jest znacznie krótszy - i nie musisz znać nazw pól z góry. Jak już wspomniałem powyżej, nie jest konieczne ustalanie kolejności pól w pamięci - ale może to być przydatne, jeśli trzeba się dowiedzieć, ile paddingu użyto.

+0

Dzięki, używanie różnic wskaźnika jest dokładnie tym, czego potrzebowałem. Tak długo, jak .Net uniemożliwia jakiekolwiek optymalizacje, pola są wykluczone lub coś w tym stylu ... –

+0

Otrzymuję komunikat "Nie mogę wziąć adresu danego wyrażenia" kompilatora, gdy próbuję zastosować operator & do pola takiego jak t.I. –

+0

@Strilanc Jeśli skopiujesz i wkleisz mój kod, będzie działał dobrze, więc musisz robić coś innego.Czy możesz zadać nowe pytanie, dlaczego to, co robisz, nie zadziała? Nie można tu zdiagnozować w komentarzach. Wiem, że napisany przeze mnie kod działa i nie zawiera również kodu 't.l' (zwróć uwagę na małe litery' l') w dowolnym miejscu, więc wiem, że musisz robić coś innego. :) –

2

Jako punkt odniesienia dla tych, którzy chcą poznać kolejność i rodzaj układu. Na przykład, jeśli typ zawiera typy nie do zarażenia.

var fields = typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); 
fields.SortByFieldOffset(); 

var isExplicit = typeof(T).IsExplicitLayout; 
var isSequential = typeof(T).IsLayoutSequential; 

Wykorzystuje metodę rozszerzenia, które napisałem:

public static void SortByFieldOffset(this FieldInfo[] fields) { 
     Array.Sort(fields, (a, b) => OffsetOf(a).CompareTo(OffsetOf(b))); 
    } 

    private static int OffsetOf(FieldInfo field) { 
     return Marshal.OffsetOf(field.DeclaringType, field.Name).ToInt32(); 
    } 

MSDN zawiera użyteczne informacje na IsLayoutSequential.

+2

'return fields.OrderBy (OffsetOf) .ToArray()' jest nieco bardziej zwięzły i niezmienny do rozruchu. –

Powiązane problemy