2009-10-02

LINQ - uwaga na temat klauzul 'where'

W poniższym przykładzie zmierzono czas wykonywania kodu w którym występuje wiele klauzul where porównując go do kodu, w którym zastąpiono je jedną klauzulą where.
using System.Linq;
using System.Collections.Generic;

namespace SpeedTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var list = Enumerable.Range(0, 1000).ToList();

            {
                System.Diagnostics.Stopwatch stopwatch = 
                    new System.Diagnostics.Stopwatch();
                stopwatch.Start();

                for (int i = 0; i < 10000; i++)
                {
                    (from num in list
                     where num != 11
                     where num != 12
                     select num).ToList().ToString();
                }

                stopwatch.Stop();

                System.Console.WriteLine(("Time #1: " + 
                    stopwatch.ElapsedMilliseconds + " [ms]"));
            }

            {
                System.Diagnostics.Stopwatch stopwatch = 
                    new System.Diagnostics.Stopwatch();
                stopwatch.Start();

                for (int i = 0; i < 10000; i++)
                {
                    (from num in list
                     where num != 11
                     where num != 12
                     where num != 13
                     where num != 14
                     select num).ToList().ToString();
                }

                stopwatch.Stop();

                System.Console.WriteLine(("Time #2: " + 
                    stopwatch.ElapsedMilliseconds + " [ms]"));
            }

            {
                System.Diagnostics.Stopwatch stopwatch = 
                    new System.Diagnostics.Stopwatch();
                stopwatch.Start();

                for (int i = 0; i < 10000; i++)
                {
                    (from num in list
                     where num != 11
                     where num != 12
                     where num != 13
                     where num != 14
                     where num != 15
                     where num != 16
                     where num != 17
                     where num != 18
                     select num).ToList().ToString();
                }

                stopwatch.Stop();

                System.Console.WriteLine(("Time #3: " + 
                    stopwatch.ElapsedMilliseconds + " [ms]"));
            }

            {
                System.Diagnostics.Stopwatch stopwatch = 
                    new System.Diagnostics.Stopwatch();
                stopwatch.Start();

                for (int i = 0; i < 10000; i++)
                {
                     (from num in list
                     where (num != 11) &&
                           (num != 12)
                     select num).ToList().ToString();
                }

                stopwatch.Stop();

                System.Console.WriteLine(("Time #4: " + 
                    stopwatch.ElapsedMilliseconds + " [ms]"));
            }

            {
                System.Diagnostics.Stopwatch stopwatch = 
                    new System.Diagnostics.Stopwatch();
                stopwatch.Start();

                for (int i = 0; i < 10000; i++)
                {
                    (from num in list
                     where (num != 11) &&
                           (num != 12) &&
                           (num != 13) &&
                           (num != 14)
                     select num).ToList().ToString();
                }

                stopwatch.Stop();

                System.Console.WriteLine(("Time #5: " + 
                    stopwatch.ElapsedMilliseconds + " [ms]"));
            }

            {
                System.Diagnostics.Stopwatch stopwatch = 
                    new System.Diagnostics.Stopwatch();
                stopwatch.Start();

                for (int i = 0; i < 10000; i++)
                {
                    (from num in list
                     where (num != 11) &&
                           (num != 12) &&
                           (num != 13) &&
                           (num != 14) &&
                           (num != 15) &&
                           (num != 16) &&
                           (num != 17) &&
                           (num != 18)
                     select num).ToList().ToString();
                }

                stopwatch.Stop();

                System.Console.WriteLine(("Time #6: " + 
                    stopwatch.ElapsedMilliseconds + " [ms]"));
            }

            {
                System.Diagnostics.Stopwatch stopwatch =
                    new System.Diagnostics.Stopwatch();
                stopwatch.Start();

                for (int i = 0; i < 10000; i++)
                {
                    List<int> nums = new List<int>();

                    foreach (int num in list)
                    {
                        if ((num != 11) &&
                            (num != 12) &&
                            (num != 13) &&
                            (num != 14) &&
                            (num != 15) &&
                            (num != 16) &&
                            (num != 17) &&
                            (num != 18))
                        {
                            nums.Add(num);
                        }
                    }

                    nums.ToString();
                }

                stopwatch.Stop();

                System.Console.WriteLine(("Time #7: " +
                    stopwatch.ElapsedMilliseconds + " [ms]"));
            }

            System.Console.ReadKey();
        }
    }
}
Wyniki: Time #1: 440 [ms] Time #2: 536 [ms] Time #3: 805 [ms] Time #4: 357 [ms] Time #5: 364 [ms] Time #6: 375 [ms] Time #7: 175 [ms] Widać, że używanie wielu klauzul where ma bardzo negatywny pływ na prędkość kodu. Jeśli zależy nam na szybkim kodzie powinniśmy się ich wystrzegać. Wszystko oczywiście zależy ile takich klauzul będzie wykonywanych. W powyższmy przykładzie jest ich 10 milionów. Korzystanie z wielu klauzul to nawyk jakiego można nabyć podczas obcowania z SQL. Kod taki jest też bardziej czytelny. W ostatnim siódmym przypadku zrezygnowaliśmy z LINQ. Przyspieszenie jest znaczące (375 -> 175).

Zapmiętywanie ustawień strony dla drukowania

Poniższy fragment kodu umożliwia nam zapamiętywanie i przywracanie ustawień drukowanej strony (System.Drawing.Printing.PageSettings). Zapamiętywane są wszystkie ustawienia które możemy ustawić: rodzaj papieru, źródło papieru, marginesy, orientacja strony. Ustawienia są pamiętane w pliku konfiguracyjnym aplikacji. Prawdopodobnie dla systemów gdzie jednostką miary jest cal, poniższy kod nie będzie dobrze liczył marginesów. Przykład zastosowania:
PageSettings.Instance.Save(pageSetupDialog.PageSettings);
PageSettings.Instance.Restore(pageSetupDialog.PageSettings);
Kod:
public class Config
{
    private static Configuration s_config;

    public static Configuration Instance
    {
        get
        {
            if (s_config == null)
            {
                s_config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
            }

            return s_config;
        }
    }
}

public class PageSettings : ConfigurationSection
{
    private static String SECTION_NAME = "pegeSettings";
    private static PageSettings s_instance;

    static PageSettings()
    {
        s_instance = Config.Instance.GetSection(SECTION_NAME) as PageSettings;

        if (s_instance == null)
        {
            s_instance = new PageSettings();
            s_instance.SectionInformation.AllowExeDefinition = ConfigurationAllowExeDefinition.MachineToLocalUser;
            Config.Instance.Sections.Add(SECTION_NAME, s_instance);
        }
    }

    public static PageSettings Instance
    {
        get
        {
            return s_instance;
        }
    }

    public void Save(System.Drawing.Printing.PageSettings a_settings)
    {
        Margins margins = PrinterUnitConvert.Convert(a_settings.Margins, 
            PrinterUnit.ThousandthsOfAnInch, PrinterUnit.TenthsOfAMillimeter);
        MarginLeft = margins.Left;
        MarginRight = margins.Right;
        MarginTop = margins.Top;
        MarginBottom = margins.Bottom;

        Landscape = a_settings.Landscape;
        PaperSizeName = a_settings.PaperSize.PaperName;
        PaperSourceName = a_settings.PaperSource.SourceName;

        Config.Instance.Save();
    }

    public void Restore(System.Drawing.Printing.PageSettings a_settings)
    {
        Margins margins = new Margins(MarginLeft, MarginRight, MarginTop, MarginBottom);
        margins = PrinterUnitConvert.Convert(margins, 
            PrinterUnit.TenthsOfAMillimeter, PrinterUnit.HundredthsOfAMillimeter);
        a_settings.Margins = margins;

        a_settings.Landscape = Landscape;

        foreach (PaperSize paper_size in a_settings.PrinterSettings.PaperSizes)
        {
            if (paper_size.PaperName == PaperSizeName)
                a_settings.PaperSize = paper_size;
        }

        foreach (PaperSource printer_source in a_settings.PrinterSettings.PaperSources)
        {
            if (printer_source.SourceName == PaperSourceName)
                a_settings.PaperSource = printer_source;
        }
    }

    [ConfigurationProperty("marginLeft", DefaultValue = 10)]
    public int MarginLeft
    {
        get
        {
            return (int)base["marginLeft"];
        }
        set
        {
            base["marginLeft"] = value;
        }
    }

    [ConfigurationProperty("marginRight", DefaultValue = 10)]
    public int MarginRight
    {
        get
        {
            return (int)base["marginRight"];
        }
        set
        {
            base["marginRight"] = value;
        }
    }

    [ConfigurationProperty("marginTop", DefaultValue = 10)]
    public int MarginTop
    {
        get
        {
            return (int)base["marginTop"];
        }
        set
        {
            base["marginTop"] = value;
        }
    }

    [ConfigurationProperty("marginBottom", DefaultValue = 10)]
    public int MarginBottom
    {
        get
        {
            return (int)base["marginBottom"];
        }
        set
        {
            base["marginBottom"] = value;
        }
    }

    [ConfigurationProperty("paperSourceName", DefaultValue = "")]
    public string PaperSourceName
    {
        get
        {
            return (string)base["paperSourceName"];
        }
        set
        {
            base["paperSourceName"] = value;
        }
    }

    [ConfigurationProperty("paperSizeName", DefaultValue = "")]
    public string PaperSizeName
    {
        get
        {
            return (string)base["paperSizeName"];
        }
        set
        {
            base["paperSizeName"] = value;
        }
    }

    [ConfigurationProperty("landscape", DefaultValue = false)]
    public bool Landscape
    {
        get
        {
            return (bool)base["landscape"];
        }
        set
        {
            base["landscape"] = value;
        }
    }
}