是否可以优化该代码?

时间:2009-01-08 00:23:16

标签: .net vb.net algorithm

我对速度感兴趣,而不是好看的代码,这就是我使用数组而不是列表(整数)的原因。

我的数组看起来像:0,1,0,1,1,0,1,0,1,1,1,0,0,1

我对每个数字的位置很感兴趣,所以我可以随后随机选择一个。

所以我所做的是循环遍历数组以获取每个1的位置编号,然后创建一个如下所示的新数组:2,4,5,7,9,10,11,14

是否可以在这里使用?我不知道

代码看起来像:

Private Function theThing() As Integer()
    Dim x As Integer
    'arIn() would be a parameter
    Dim arIn() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim ar() As Integer = Nothing
    Dim arCount As Integer = -1

    For x = 1 To arIn.GetUpperBound(0)
        If arIn(x) = 1 Then
            arCount += 1
        End If
    Next

    If arCount > -1 Then
        'using redim preseve is slower than the loop above
        ReDim ar(arCount)

        arCount = 0
        For x = 1 To arIn.GetUpperBound(0)
            If arIn(x) = 1 Then
                ar(arCount) = x
                arCount += 1
            End If
        Next
    End If

    Return ar
End Function

*编辑*

目前的解决方案(速度提高10%到15%)现在

Private Function theThing() As Integer
    Dim ar() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim arLenght As Integer = ar.GetUpperBound(0)
    Dim arCount As Integer = 0
    Dim x As Integer

    For x = 1 To arLenght
        If ar(x) = 1 Then
            ar(arCount) = x
            arCount += 1
        End If
    Next

    dim r As New Random()

    Return ar(r.Next(arCount))
End Function

我不认为它可以比这更优化,除非有人找到一种方法来完成解决方案所做的事情,但速度更快

在这个问题出现之前,我的整个事情每10秒就可以做25500次。

现在,它可以一直超过32250,增加21%,谢谢!

11 个答案:

答案 0 :(得分:8)

不是存储整数数组,为什么不将它们全部放入一个整数?

oldArray = [0, 1, 1, 0, 1]
newValue =  22     (binary 10110)

如果要检查是否设置了特定的位位置,请按两位与该位置的幂进行逐位比较:

is position 2 set?
value:    10110
4 (2^2):  00100
result:   00100 --> true

is position 0 set?
value:    10110
1 (2^0):  00001
result:   00000 --> false

搜索按位比较,你应该找到很多帮助。

以下是一些可能有用的Stack Overflow问题:
What are bitwise operators?
How do you set, clear, and toggle a single bit?

答案 1 :(得分:2)

关于原始算法的一些提示:

  1. 尝试将arIn.GetUpperBound(0)的结果存储在变量中。我不知道VB如何使它成为循环,但每次迭代都有可能调用该函数。你应该检查一下。
  2. If arCount > -1总是如此。删除它。
  3. 如果您希望保持相同的输入/输出,那么我认为还有很多其他方法可以改进。

    现在,如果你想要一个能够进行随机选择的函数,那么它可能会更好一些。我会用C#写,因为我知道它更好。你应该能够理解:

    public static int GetRandomSetBit(int[] AllBits)
    {
        // Perhaps check here if AllBits is null/empty. I'll skip that for now.
    
        int L = AllBits.Length;
        int SetBitCount = 0;
    
        // No point to save a few bytes here. Also - you can make a global array
        // large enough for all occasions and skip allocating it every time.
        // In that case consider automatic resizing and watch out for
        // multithreading issues (if you use several threads).
        int[] GoodPositions = new int[L];
    
        for ( int i = 0; i < L; i++ )
            if ( AllBits[i] != 0 )
            {
                GoodPositions[SetBitCount] = i;
                SetBitCount++;
            }
         Random r = new Random(); // Should use one global instance
         return GoodPositions[r.Next(SetBitCount)];
    }
    

    我担心它不会比这更好。除非你能以某种方式改变输入/输出或要求,否则不会。

答案 2 :(得分:1)

我发现很难相信redim保存会比你的循环慢,除非它本身在循环内。

在这种情况下,对于原始速度,不要只计算ar的1的数量来设置ar的大小。由于ar 永远不会大于arIn,只需将其设置为相同的大小并在末尾进行redim-preserve(不会慢,因为它在循环外部并且将始终修剪,而不是扩展 - VB希望可以就地执行此操作而不是分配更多内存。此外,如果VB每次通过循环计算它(可能是允许使用ReDim),arIn的高速缓存大小。

Private Function theThing() As Integer()
    Dim x As Integer
    'arIn() would be a parameter
    Dim arIn() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim ar(arIn.GetUpperBound(0)) As Integer
    Dim arCount As Integer
    Dim arInCount As Integer

    arCount = 0
    arInCount = arIn.GetUpperBound(0)
    For x = 1 To arInCount
        If arIn(x) = 1 Then
            ar(arCount) = x
            arCount += 1
        End If
    Next

    ReDim Preserve ar(arCount)
    Return ar
End Function

或者,如果您略微调整返回的内容,则可以完全删除redim。使返回数组大于输入数组,并使用第一个元素控制您将随机选择的数组部分。

对于您的示例,返回的数组将是:

{8,2,4,5,7,9,10,11,14,?,?,?,?,?,?} (? values are irrelevant).
 ^ <-------+--------> <----+---->
 |         |               |
 |         |               +-- These are unused.
 |         |
 |         +-- These are used.
 |
 +-- This is the count of elements to use.

该代码将是:

Private Function theThing() As Integer()
    Dim x As Integer
    'arIn() would be a parameter
    Dim arIn() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim ar(arIn.GetUpperBound(0)+1) As Integer
    Dim arCount As Integer
    Dim arInCount As Integer

    arCount = 0
    arInCount = arIn.GetUpperBound(0)
    For x = 1 To arInCount
        If arIn(x) = 1 Then
            ar(arCount) = x
            arCount += 1
        End If
    Next

    ar(0) = arCount
    Return ar
End Function

然后,在您的代码中从ar中选择一个随机值,而不是:

rndval = rnd(ar.GetUpperBound)

使用:

rndval = rnd(ar(0) + 1)

答案 3 :(得分:1)

禁用溢出检查。
 项目属性 - &gt;编译 - &gt;高级编译选项 - &gt;删除整数溢出检查 如果需要对项目的其余部分进行溢出检查,可以将代码移动到新项目(例如DLL)并禁用该新项目的溢出检查。

还要确保您正在运行发布版本(已启用优化)并且您没有对其进行调试。

编辑:我得到8.5秒(12秒,如果我在For I'm用于测试中声明数组)调用该函数50毫秒。如果您只获得32000,则使用非常大的输入或某些东西会降低代码速度。例如,如果您计算程序内部的时间并且在分析器中运行它,则会得到错误的结果,因为分析可能会大大减慢程序的速度。像Slow methods calls这样的故障可能会影响性能。

答案 4 :(得分:1)

我认为当Recursive引用BitArray时,他的意思是这样的:

using System.Collections.Generic;
using System;
using System.Collections;

class Program
{
    static void Main(string[] args)
    {
        var input = new BitArray(new byte[] { 0x5A /*01011010b*/
                                        , 0xE4 /*11100100b*/ });
        var output = new List<int>();
        var offset = 1;
        foreach (var bit in input)
        {
            if (bit)
            {
                output.Add(offset);
            }
            offset++;
        }
    }
}

答案 5 :(得分:0)

速度有多少项?

更改项目?

编译时间或执行时间?

有什么空间限制?

一个已知的策略是将几个组合在一起并写出所有组合及其结果: 0000 - &gt; 0001 - &gt; 4 0010 - &gt; 3 0011 - &gt; 3,4 0100 - &gt; 2 0101 - &gt; 2,4 0110 - &gt; 2,3 ...

为什么要从这个二进制表示转换为随机选择一位?这不太可能有助于提高绩效。最好将它们分组为8位,使用表格告诉你组中有多少1,并重复5次。那么你知道有多少个。随机做一遍,然后查找所选的那个。

答案 6 :(得分:0)

您可能会发现使用For Each并且初始化为至少输入数组长度的列表比索引器更快:

If arIn.Length > 0 Then
  Dim lstOut As New List(Of Integer)(arIn.Length)
  Dim ix As Integer = 0
  For Each val As Integer In arIn
    If val = 1 Then
      lstOut.Add(ix)
    End If
    ix = ix + 1
  End If
  Return lstOut.ToArray()
Else
  Return Nothing
End If

但你必须测试它。

答案 7 :(得分:0)

这就是BitArray的目的。

答案 8 :(得分:0)

也许您可以使用转换表来挤压最高性能。然后应用下面的算法:

抱歉,我不再熟悉VB,所以我必须写C#。这是整个代码的一部分,因为整个lookupTable将有256个项目。

using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        var input = new byte[] { 0x5A /*01011010b*/, 0xE4 /*11100100b*/ };
        var output = new List<int>();
        var lookupTable = new int[][] { new int[0] /*00000000b*/
                                           , new int[] { 8 }    /*00000001b*/
                                           , new int[] { 7 }    /*00000010b*/
                                           , new int[] { 7,8 }  /*00000011b*/
                                           , new int[] { 6 }    /*00000100b*/
                                           , new int[] { 6,8 }  /*00000101b*/
                                           , new int[] { 6,7,8 }  /*00000111b*/
                                          };
        int offset = 0;
        foreach (var value in input)
        {
            foreach (var position in lookupTable[(int)value])
            {
                output.Add(position + offset);
            }
            offset += 8;
        }
    }
}

答案 9 :(得分:0)

如果很容易生成一个略大于阵列长度的素数(取决于它的大小,这可能也可能不容易),而你并不关心完全均匀随机,那么你可以生成该范围内的随机数并生成该循环。它应该在几次迭代中找到答案(基于1的密度)。 c#中的源代码,因为我不记得vb语法:

int RandomInArray(int[] ar)
{
    int p = GenerateSlightlyLargerPrime(ar.Length);

    int x = random.nextInt(p);
    int i = x;
    do
    {
       if (i < ar.Length && ar[i] == 1)
           return i;
       i = (i + x) % p;
    } while (i != x);
    return -1;
}

请注意,这不是100%均匀随机,但它应该非常接近。

答案 10 :(得分:0)

我尝试过不同的方法。我们的想法是在找到对应于1的条目时继续进行随机输入和退出。这种解决方案并不完美,因为没有使用某些可能会或可能不会破坏随机性的random。另外,速度很大程度上取决于“1”的密度。代码如下:

public static int GetRandomSetBit(int[] AllBits)
{
    int L = AllBits.Length;
    Random r = new Random();    // Better use a global instance as this hurt performance a lot
    do
    {
         selected = r.Next(L);

    } while (AllBits[selected] == 0);
    return selected;
}

在我的电脑中,如果将Random对象的创建移动到全局,如果30中有5“1”,它可以在11s左右运行50000000次试验。如果有最多15“1”,所需的时间缩短到5秒左右。

与Vilx建议的代码相比,他的代码可以在13秒左右在我的电脑上运行50000000次试验