List <t>的多列并行排序

时间:2016-11-09 18:10:15

标签: c# linq sorting parallel-processing task-parallel-library

对于List<T>,我需要实现多列排序,其中列名和排序方向在运行时是已知的。我使用System.Linq.Dynamic OrderBy API,它可以将列名和排序方向作为连接字符串,因此代码可以工作:

List<T> data = DataCollection; // Stored in Cache
var sortedData = data.OrderBy("Col1 asc, Col2 desc, Col3 asc,Col4 asc");

挑战是当数据大小增加到1 million+个记录时,相同的排序操作会大大减慢,因为没有魔杖。

现在我想了解在Parallel模式下是否有相同操作的方法。以下是我正在考虑的选项:

选项1:

  • 将数据集合分成较小的子集,例如每个100 K,并在每个子集上运行Sort,但接下来的挑战是如何合并各个集合,在我的理解中没有方便的机制集成已排序的子集

选项2

当我正在寻找选项时,遇到了List<int>的以下并行模式,其中递归Parallel Sort也在内部调用递归序列排序:

public class CustomSort
{   
    // Fetch Partition
    public static int Partition(List<int> list, int left, int right)
    {
        int start = left;
        int pivot = list[start];
        left++;
        right--;

        while (true)
        {
            while (left <= right && list[left] <= pivot)
                left++;

            while (left <= right && list[right] > pivot)
                right--;

            if (left > right)
            {
                list[start] = list[left - 1];
                list[left - 1] = pivot;

                return left;
            }


            int temp = list[left];
            list[left] = list[right];
            list[right] = temp;

        }
    }

    // Quick Sort serial
    public static void QuickSort(List<int> list, int left, int right)
    {
        if (list == null || list.Count <= 1)
            return;

        if (left < right)
        {
            int pivotIdx = Partition(list, left, right);
            QuickSort(list, left, pivotIdx - 1);
            QuickSort(list, pivotIdx, right);
        }
    }

    // Quick Sort Parallel
    public static void QuickSortParallel(List<int> list, int left, int right)
    {
        if (list == null || list.Count <= 1)
            return;

        if (left < right)
        {
            int pivotIdx = Partition(list, left, right);        

            Task leftTask = Task.Run(() => QuickSort(list, left, pivotIdx - 1));
            Task rightTask = Task.Run(() => QuickSort(list, pivotIdx, right));

            Task.WaitAll(new[] { leftTask, rightTask });

        }
    }
}

问题:

  • 有没有更好的方法来实现同样的目标?
  • 对于整数而言,简单,如何翻译我的multi column sort版本,因为选择分区将是一件复杂的事情

任何指针,可以让我设置正确的路径

1 个答案:

答案 0 :(得分:2)

最合乎逻辑的选择是从LINQ切换到Parallel LINQ (PLINQ)

不幸的是,尽管System.Linq.Dynamic OrderBy方法有效,但它实际上会遇到Enumerable方法重载,因此对ParallelQuery<T>无效,这需要绑定到相应的ParallelEnumerable重载。动态LINQ OrderBy实现也使用内部类,因此无法在外部扩展它(没有源代码)。

仍然存在解决方案。您可以使用以下自定义扩展方法。它的作用是使用动态LINQ构建虚假查询,然后使用相对简单的ExpressionVisitor替换相应的Queryable调用相应的ParallelEnumerable调用:

public static class DynamicPLINQ
{
    public static OrderedParallelQuery<T> OrderBy<T>(this ParallelQuery<T> source, string ordering, params object[] values)
    {
        var query = Enumerable.Empty<T>().AsQueryable();
        var orderedQuery = query.OrderBy(ordering, values);
        var binder = new ParallelQueryBinder();
        binder.source = query;
        binder.target = source;
        var queryExpr = binder.Visit(orderedQuery.Expression);
        return (OrderedParallelQuery<T>)query.Provider.Execute(queryExpr);
    }

    class ParallelQueryBinder : ExpressionVisitor
    {
        public object source;
        public object target;
        protected override Expression VisitConstant(ConstantExpression node)
        {
            if (node.Value == source)
                return Expression.Constant(target);
            return base.VisitConstant(node);
        }
        protected override Expression VisitUnary(UnaryExpression node)
        {
            if (node.NodeType == ExpressionType.Quote)
                return Visit(node.Operand);
            return base.VisitUnary(node);
        }
        static readonly string[] Methods = { "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending" };
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Method.IsStatic && node.Method.DeclaringType == typeof(Queryable) && Methods.Contains(node.Method.Name))
            {
                var arguments = node.Arguments.Select(Visit).ToArray();
                var result = Expression.Call(typeof(ParallelEnumerable), node.Method.Name, node.Method.GetGenericArguments(), arguments);
                return result;
            }
            return base.VisitMethodCall(node);
        }
    }
}

现在您可以使用这样的PLINQ服务:

var sortedData = data.AsParallel()
    .OrderBy("Col1 asc, Col2 desc, Col3 asc,Col4 asc")
    .ToList();

并比较表现。