【问题标题】:Java Optimizing arithmetic and Assignment Operators for large inputJava 优化大输入的算术和赋值运算符
【发布时间】:2014-02-06 17:36:56
【问题描述】:

我有一段代码在时钟速度方面必须运行得非常快。该算法已经在 O(N) 中。需要2秒,需要1秒。对于大多数 A.length 输入 ~ 100,000 需要 0.3 秒,除非特定的代码行被调用了极端次数。 (对于深奥的编程挑战)

它使用算术级数的计算,即 1,2,..N -> 1,3,4,10,15.. 可以表示为 n*(n+1)/2 我循环这个方程几十万次。 我无权访问输入,也无法显示它。我能得到的唯一信息是运行时间。 特别是等式是:

s+=(n+c)-((n*(n+1))/2);

s 和 c 的取值范围为 0 到 10 亿

n 的范围可以是 0 到 100,000

就时钟速度而言,编写此语句的最有效方法是什么? 我听说除法比乘法花费更多时间,但除此之外,我无法确定将其写在一行或多行中是否更有效。 除法和乘法与乘法然后除法? 创建自定义整数类型也会有很大帮助吗?

根据要求进行编辑,输入小写的完整代码(对不起,如果它很难看,我只是一直在剥离它):

public static void main(String[] args) {

        int A[]={3,4,8,5,1,4,6,8,7,2,2,4};//output 44
        int K=6;
        //long start = System.currentTimeMillis();;
        //for(int i=0;i<100000;i++){
            System.out.println(mezmeriz4r(A,K));
        //}
        //long end = System.currentTimeMillis();;

//      System.out.println((end - start) + " ms");

    }
    public static int mezmeriz4r(int[]A,int K){
        int s=0;
        int ml=s;
        int mxl=s;
        int sz=1;
        int t=s;
        int c=sz;
        int lol=50000;
        int end=A.length;
        for(int i=sz;i<end;i++){
            if(A[i]>A[mxl]){
                mxl=i;
            }else if(A[i]<A[ml]){
                ml=i;
            }
            if(Math.abs(A[ml]-A[mxl])<=K){
                sz++;
                if(sz>=lol)return 1000000000;
                if(sz>1){
                    c+=sz;
                }
            }else{
                if(A[ml]!=A[i]){
                    t=i-ml;
                    s+=(t+c)-((t*(t+1))/(short)2);
                    i=ml;
                    ml++;
                    mxl=ml;
                }else{
                    t=i-mxl;
                    s+=(t+c)-((t*(t+1))/(short)2);
                    i=mxl;
                    mxl++;
                    ml=mxl;
                }
                c=1;
                sz=0;
            }
        }
        if(s>1000000000)return 1000000000;
        return s+c;
    }

挑战归来:

检测到的时间复杂度:

O(N)

测试时间结果

示例 示例测试 0.290 秒。好的

单人 单个元素 0.290 秒。好的

双倍 两个元素 0.290 秒。好的

small_functional 小型功能测试 0.280 秒。好的

small_random 小随机序列长度 = ~100 0.300 s。好的

small_random2 小随机序列长度 = ~100 0.300 s。好的

medium_random 混沌中等序列长度 = ~3,000 0.290 s。好的

大范围 大范围测试,长度 = ~100,000 2.200 s。超时错误 运行时间:>2.20 秒,时限:1.02 秒。

大随机 随机大序列长度 = ~100,000 0.310 s。好的

large_answer 大答案测试 0.320 秒。好的

large_extreme 所有最大值 = ~100,000 0.340 秒。好的

【问题讨论】:

  • 发布你的代码,然后我可以尝试改进它
  • 既然您知道n&gt;0,您可以将/2 替换为&gt;&gt;1。与此相比,其他一切都微不足道。
  • 对于真正的测量,使用预热:启动循环两次,第一次不测量时间。这使 JVM 有时间正确编译您的方法。
  • @SargeBorsch:是的,还不够。我应该推荐一个基准测试框架,但是对于像这样复杂的事情,花一秒钟的时间进行一次微不足道的预热就足够了。如果您的意思是重复计数,是的,如果我对精确值感兴趣,我会让它等待一两分钟。但同样,这可能就足够了。
  • @woodlumhoodlum 如果更改顺序,您不需要 Math.abs。平均值而不是 if(Math.abs(A[ml]-A[mxl])

标签: java algorithm optimization mathematical-optimization


【解决方案1】:

借助一点代数,您可以简单地将表达式 (n+c)-((n*(n+1))/2)c-((n*(n-1))/2) 删除一个加法运算。然后您可以将2 的除法替换为1 的右移位,这比除法更快。换个试试

s+=(n+c)-((n*(n+1))/2);

s+=c-((n*(n-1))>>1);

【讨论】:

  • 大多数优化器会为您更改分区。
  • @David Ehrmann:这种优化只对正值有效。因此编译器无法进行这种优化。在浏览器中自己尝试一下:(测试 tis 的小程序)[rinneberg.de/programming/opti.htm#shift_statt_div_english]
  • @DavidEhrmann 根据Does Java optimize division by powers of two to bitshifting?,Java 中不会发生这种优化。
  • @MrSmith42:编译器javac 做不到,但JVM 可以。如果它不能证明股息是非负的,它会使用移位和条件修复。在这里,红利肯定是非负的,但我怀疑 JVM 是否那么聪明。
  • @MrSmith42:更新:对不起,如果溢出,股息可能会变为负数。
【解决方案2】:

我无权验证所有输入。和时间范围。但这肯定会运行 O(N)。并有所改善。运行并让我知道您的反馈。如有必要,我将提供详细信息

public static int solution(int[]A,int K){
    int minIndex=0;
    int maxIndex=0;
    int end=A.length;
    int slize = end;
    int startIndex = 0;
    int diff = 0;
    int minMaxIndexDiff = 0;
    for(int currIndex=1;currIndex<end;currIndex++){
        if(A[currIndex]>A[maxIndex]){
            maxIndex=currIndex;
        }else if(A[currIndex]<A[minIndex]){
            minIndex=currIndex;
        }
        if( (A[maxIndex]-A[minIndex]) >K){
            minMaxIndexDiff= currIndex- startIndex;
            if (minMaxIndexDiff > 1){
                slize+= ((minMaxIndexDiff*(minMaxIndexDiff-1)) >> 1);
                if (diff > 0 ) {
                    slize = slize + (diff * minMaxIndexDiff);
                }
            }

            if (minIndex == currIndex){
                diff = currIndex - (maxIndex + 1);
            }else{
                diff = currIndex - (minIndex + 1);
            }
            if (slize > 1000000000) {
                return 1000000000;
            }
            minIndex = currIndex;
            maxIndex = currIndex;
            startIndex = currIndex;
        }
    }
    if ( (startIndex +1) == end){
        return slize;
    }
    if (slize > 1000000000) {
        return 1000000000;
    }
    minMaxIndexDiff= end- startIndex;
    if (minMaxIndexDiff > 1){
        slize+= ((minMaxIndexDiff*(minMaxIndexDiff-1)) >> 1);
        if (diff > 0 ) {
            slize = slize + (diff * minMaxIndexDiff);
        }
    }

    return slize;
}

【讨论】:

  • 他们刚刚从他们的网站上删除了挑战:(,除非他们最终在他们的培训网站上发布问题,否则我将无法检查。我以为我们还有几天......看起来like yours 不会递归,并且可能足够快。至少感谢您的帮助。他们发布了一个新的:codility.com
  • 好的,一切顺利。在时间允许的情况下,将添加 cmets/details。我从其他答案中学到了很多优化技术。谢谢你。
  • 你的不适用于这个测试用例,答案应该是 15:int A[]={10, 1, 7, 2, 2, 9, 3, 6, 2, 7, 3 , 3}; int k=3;
【解决方案3】:

去掉 for 循环中的 System.out.println() :) 你会惊讶于你的计算速度会有多快

【讨论】:

  • main 中的 for 循环仅用于测试。实际运行时没有打印语句。我注释掉了循环。输入 100000,我的意思是 A.length。
【解决方案4】:

嵌套分配,i。 e.而不是

t=i-ml;
s+=(t+c)-((t*(t+1))/(short)2);
i=ml;
ml++;
mxl=ml;

类似

s+=((t=i-ml)+c);
s-=((t*(t+1))/(short)2);
i=ml;
mxl=++ml;

有时会出现在 OpenJDK 源代码中。它主要导致将*load字节码指令替换为*dups。根据我的实验,它确实提供了非常少的加速,但它是超hadrcore,我不建议手动编写这样的代码。

【讨论】:

  • 大大提高了 0.9 秒。(虽然服务器速度不断变化,因此很难确定),但仍然不完全。结果-> 运行时间:>1.17 秒,时间限制:0.98 秒。我将不得不重写我的算法。
【解决方案5】:

我会尝试以下方法并在每次更改后分析代码以检查速度是否有任何提升。


替换:

if(Math.abs(A[ml]-A[mxl])<=K)

int diff = A[ml]-A[mxl];
if(diff<=K && diff>=-K)

替换

/2

通过

>>1

替换

ml++;
mxl=ml;

通过

mxl=++ml;

也许避免对同一元素的数组访问(java的内部边界检查可能需要一些时间)

所以至少在本地变量中保持A[i]

【讨论】:

  • 您认为ml++; mxl=ml; 生成的字节码与mxl=++ml; 不同?
  • @Holger:如果编译器很聪明,则不会,但您可以减少对内存的访问次数。我认为值得一试。正如我所写,您应该始终描述您所做的每一项更改。
  • 我剧透一下:字节码相同的。 iinc &lt;ml&gt;; iload &lt;ml&gt;; istore &lt;mxl&gt; 并且没有办法对其进行不同的编码(好吧,当您接受更大的代码大小时会出现这种情况)。如果您想在其中发现智能,那将是因为编译器足够智能,可以以与这两个语句相同的方式对您的“改进”进行编码。
  • math.abs 或 mxl=++ml;为一些返回的测试用例(我无权访问)打破了它。 >>1 工作正常。我在上面添加了挑战输出。
  • @woodlumhoodlum:我会对出了什么问题感兴趣。如果你能缩小问题范围就好了。
【解决方案6】:

我会先创建一个 C 版本,然后看看“直接访问金属”的速度有多快。很有可能,您正在尝试优化已经优化到极限的计算。

【讨论】:

    【解决方案7】:

    我会尝试消除这条线if(Math.abs(A[ml]-A[mxl])&lt;= 通过更快的自计算 abs 版本,它是内联的,而不是方法调用!

    演员表(短)没有帮助, 但尝试右移运算符 X >>1 而不是 x / 2

    删除 System.out.println() 可以加快 1000 倍。 但要小心,否则你的整个算法可能会被 VM 删除,因为你不使用它。 旧代码:

    for(int i=0;i<100000;i++){
                System.out.println(mezmeriz4r(A,K));
    }
    

    新代码:

    int dummy = 0;
        for(int i=0;i<100000;i++){
              dummy =   mezmeriz4r(A,K);
        }
    //Use dummy otherwise optimisation can remove  mezmeriz4r
    System.out.print("finished: " + dummy);
    

    【讨论】:

    • 我认为现代 JVM 会大量优化库方法调用,例如 Math.abs(如果不这样做就有点愚蠢)
    • 如果你想最快,不要依赖JVM优化,即使sin()是“手工”计算的,而不是使用CPU sin()。由于 java 1.4 它的速度较慢(Strict.Math)
    • @SargeBorsch:我敢打赌他们会这样做。但也许像int diff = A[ml]-A[mxl]; if (diff &lt;= K &amp; -diff &lt;= K) {...} 这样的东西可能会更快。
    • @AlexWien:Math.abs(int) 肯定不必处理 NaN 或 Infinity。
    • @AlexWien 这是真的。但仅适用于浮点类型,这里一切都是整数。
    猜你喜欢
    • 2011-11-16
    • 1970-01-01
    • 2015-03-19
    • 1970-01-01
    • 2015-08-29
    • 2015-05-02
    • 1970-01-01
    • 2012-10-05
    • 2013-03-27
    相关资源
    最近更新 更多