`

可笑的优化

阅读更多

这几天没事做的时候都会上projecteuler.net上面去做题,其中14题是这样的:
he following iterative sequence is defined for the set of positive integers:

n → n /2 (n is even)
n → 3n + 1 (n is odd)

Using the rule above and starting with 13, we generate the following sequence:

13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1

It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1.

Which starting number, under one million, produces the longest chain?

 

    题目并不难理解,这个据说是著名的角谷猜想,现在要找到100万以下的数字中展开这个链最长的数字是多少。如果我一开始就直接按照题意来解答,这个题目花不了几分钟,直接暴力法。然而我却想的太多了,我猜想在计算这个链条长度的过程中会不会有很多数字会重复计算,如果加上缓存 以前计算的结果是否能节约比较多的时间?那么第一次解答如下:

<!---->#include < iostream >
#include
< map >
#include
< windows.h >
using   namespace  std;
unsigned 
long  produce_term(unsigned  long  n)
{
    
if (n & 1 )
        
return   3 * n + 1 ;
    
else
        
return  n >> 1 ;
}
int  main()
{
    map
< unsigned  long , int >  counters;
    
int  max_i = 0 ;
    
int  max_count = 0 ;
    DWORD tick1,tickPassed;
    tick1 
=  GetTickCount(); 
    
for ( int  i = 1 ;i < 1000000 ;i ++ )
    {
        
int  sum = 2 ;
        unsigned 
long  term = i;
        
while ((term = produce_term(term)) != 1 )
        {
            
if (counters[term]){
                sum
+= counters[term];
                
break ;
            }
else
                sum
+= 1 ;
        }

        
if (sum > max_count)
        {
            max_i
= i;
            max_count
= sum;
            counters[i]
= sum;
        }

    }
    tickPassed 
=  GetTickCount() - tick1; 
    cout
<< tickPassed << endl;
    cout
<< max_i << endl << max_count << endl;
    
return   0 ;
}

  
    遗憾的是,这个版本跑了快13分钟,太让人难以接受了。那么是否能优化下?怎么优化?我的机器是双核的,跑这个单进程单线程的程序只利用了一半的CPU,那么能不能搞成两个线程 来计算?缓存需要在两个线程之间做同步,显然读的多,写的少,应该采用读写锁 。OK,第二个版本利用ACE的线程封装实现如下:

<!---->#include < iostream >
#include
< map >
#include 
" ace/Thread_mutex.h "
#include 
" ace/Synch.h "
#include 
" ace/Thread_Manager.h "
using   namespace  std;
class  ThreadSafeMap
{
public :
    ThreadSafeMap()
    {
    }
    
int   get (unsigned  long  n)
    {
        ACE_READ_GUARD_RETURN(ACE_RW_Thread_Mutex,guard,mutex_,
0 );
        
return  counters_[n];
    }
    
int  put(unsigned  long  key, int  value)
    {
        ACE_WRITE_GUARD_RETURN(ACE_RW_Thread_Mutex,guard,mutex_,
- 1 );
        counters_[key]
= value;
        
return   0 ;
    }

private :
    map
< unsigned  long , int >  counters_;
    ACE_RW_Thread_Mutex mutex_;
};
unsigned 
long  produce_term(unsigned  long  n)
{
    
if (n & 1 )
        
return   3 * n + 1 ;
    
else
        
return  n >> 1 ;
}
static  ThreadSafeMap counters;
ACE_THR_FUNC_RETURN run_svc (
void   * arg)
{
    
int  max_i = 0 ;
    
int  max_count = 0 ;
    
for ( int  i = 500001 ;i < 1000000 ;i ++ )
    {
        
int  sum = 2 ;
        unsigned 
long  term = i;
        
while ((term = produce_term(term)) != 1 )
        {
            
if (counters. get (term)){
                sum
+= counters. get (term);
                
break ;
            }
else
                sum
+= 1 ;
        }

        
if (sum > max_count)
        {
            max_i
= i;
            max_count
= sum;
            counters.put(i,sum);
        }

    }
    cout
<< max_i << endl << max_count << endl;
    
return   0 ;
}
int  main( int  ac, char *  argv[])
{
    
if  (ACE_Thread_Manager::instance () -> spawn (
        
//  Pointer to function entry point.
        run_svc,
        
//  <run_svc> parameter.
        NULL,
        THR_DETACHED 
|  THR_SCOPE_SYSTEM)  ==   - 1 )
        
return   - 1 ;
    
int  max_i = 0 ;
    
int  max_count = 0 ;

    
for ( int  i = 1 ;i < 500000 ;i ++ )
    {
        
int  sum = 2 ;
        unsigned 
long  term = i;
        
while ((term = produce_term(term)) != 1 )
        {
            
if (counters. get (term)){
                sum
+= counters. get (term);
                
break ;
            }
else
                sum
+= 1 ;
        }

        
if (sum > max_count)
        {
            max_i
= i;
            max_count
= sum;
            counters.put(i,sum);
        }

    }
    cout
<< max_i << endl << max_count << endl;
    
return  ACE_Thread_Manager::instance () -> wait ();
}

   
    将数据分成了两半,利用两个线程来计算,果然快了一点,快了多少呢?从13分钟减少到9分钟,CPU利用率也到了100%,内存占用也降低了一半,似乎成绩不错呀。正在沾沾自喜之际,突然想起,能不能简单地暴力破解,咱不搞缓存,不搞多线程,看看效果怎么样。那么第三个版本简单实现如下:

<!---->#include < iostream >
using   namespace  std;
unsigned 
long  produce_term(unsigned  long  n)
{
    
if (n & 1 )
        
return   3 * n + 1 ;
    
else
        
return  n >> 1 ;
}
int  main()
{
  
int  max_i;
  
int  max_count = 0 ;
  
for ( int  i = 1 ;i < 1000000 ;i ++ )
  {
     
int  count = 2 ;
     unsigned 
long  term = i;
     
while ((term = produce_term(term)) > 1 )
         count
+= 1 ;
     
if (count > max_count){
           max_i
= i;
           max_count
= count;
     }
  }
  cout
<< max_i << endl << max_count << endl;
  system(
" pause " );
  
return   0 ;
}


    程序执行的结果让我惊掉了下巴,竟然只执行了1秒多,换成java也是一样。什么缓存、多线程,全抛到了九霄云外。

     总结教训,想当然的性能估计是愚不可及的,想当然的优化是愚不可及的,简单直接才是美!

分享到:
评论
6 楼 yangguo 2010-09-27  
暴力美学。
5 楼 dennis_zane 2009-02-04  
测了下,缓存命中率竟然是0,汗
4 楼 dennis_zane 2009-02-04  
@hax
又用java验证了一下,结果是一样的
import java.util.*;

public class Test {
	static long produce_term(long n) {
		if ((n & 1) != 0)
			return 3 * n + 1;
		else
			return n >> 1;
	}

	public static void main(String args[]) {
		test1();
		test2();
	}

	private static void test1() {
		long start = System.currentTimeMillis();
		int max_i = 0;
		int max_count = 0;
		for (int i = 1; i < 1000000; i++) {
			int count = 2;
			long term = i;
			while ((term = produce_term(term)) > 1) {
				count += 1;
			}
			if (count > max_count) {
				max_i = i;
				max_count = count;
			}
		}
		System.out.println("max_i=" + max_i + ",max_count=" + max_count);
		System.out.println("timed:" + (System.currentTimeMillis() - start));
	}

	static void test2() {
		Map<Integer, Integer> cache = new HashMap<Integer, Integer>();
		long start = System.currentTimeMillis();
		int max_i = 0;
		int max_count = 0;
		for (int i = 1; i < 1000000; i++) {
			int count = 2;
			long term = i;
			while ((term = produce_term(term)) > 1) {
				if (cache.containsKey(term))
					count += cache.get(term);
				else
					count++;
			}
			if (count > max_count) {
				max_i = i;
				max_count = count;
				cache.put(i, count);
			}
		}
		System.out.println("max_i=" + max_i + ",max_count=" + max_count);
		System.out.println("timed:" + (System.currentTimeMillis() - start));
	}
}

3 楼 dennis_zane 2009-02-04  
@hax
测了下你给的代码,问题在于n溢出了,变成了负数,你可以查看n=837799时n的计算链条

要注意到在计算的过程中n一定会超出100万这个范围的,所以在cpp中用了unsigned  long


2 楼 dennis_zane 2009-02-04  
@hax
你的答案是错误的,正确的是837799

数据量是100万,而非100000
1 楼 hax 2009-02-04  
你的程序有问题吧。用javascript写了一个测试,明显缓存是有效的。

[code=JavaScript]
var MAX = 100000

new function () {
var start = new Date().getTime()
var max = 0, maxn
var n, count
for (var i = 2; i < MAX; i++) {
n = i, count = 0
do {
if ((n & 1) === 0) n = n >> 1
else n = (n * 3 + 1) >> 1
count++
} while(n > 1);
if (count > max) {
max = count
maxn = i
}
}
var end = new Date().getTime()
say (maxn, max, end - start)
}

new function () {
var start = new Date().getTime()
var max = 0, maxn
var cache = []
var n, count
for (var i = 2; i < MAX; i++) {
n = i, count = 0
do {
if ((n & 1) === 0) n = n >> 1
else n = (n * 3 + 1) >> 1
if (cache[n]) {
count += cache[n] + 1
break
}
count++
} while(n > 1);
cache[i] = count
if (count > max) {
max = count
maxn = i
}
}
var end = new Date().getTime()
say (maxn, max, end - start)
}


注:需要自己写一个say函数来打印输出。

如果你使用Google Chrome,可以把MAX设为100000,因为Chrome的V8引擎对整数运算做了优化,所以比其他浏览器要快一个数量级。在我的机器上结果是:

910107 299 1541 910107 299 485

也就是不缓存用了1.5秒,缓存的用了0.5秒不到。

相关推荐

    SqlServer 执行计划及Sql查询优化初探

    网上的SQL优化的文章实在是很多,说实在的,我也曾经到处找这样的文章,什么不要使用IN了,什么OR了,什么AND了,很多很多,还有很多人拿出仅几S甚至几MS的时间差的例子来证明着什么(有点可笑),让许多人不知道其是...

    华三无线优化配置指导书

    华三无线优化配置指导书

    快速创建可笑的词法分析器-Rust开发

    创建可笑的快速Lexers徽标创建可笑的快速Lexers。 徽标有两个目标:使创建Lexer变得容易,因此您可以专注于更复杂的问题。 要使生成的Lexer比您用手编写的任何内容都要快。 为了实现这些目标,徽标:将所有令牌定义...

    MSSQL优化之探索MSSQL执行计划(转)

    网上的SQL优化的文章实在是很多,说实在的,我也曾经到处找这样的文章,什么不要使用IN了,什么OR了,什么AND了,很多很多,还有很多人拿出仅几S甚至几MS的时间差的例子来证明着什么(有点可笑),让许多人不知道其是...

    症结:Emacs可笑有用的扩展的集合

    Crux这个名字在英语中有关键、核心之意,暗示了这些扩展对于优化Emacs工作流程的重要性。 Crux的扩展覆盖了编辑、导航、代码补全、项目管理等多个方面,旨在提高生产力,减少不必要的操作。下面我们将深入探讨其中...

    TOScrollBar:交互式滚动条,用于遍历可笑的大量滚动视图

    它的设计使其外观和行为类似于标准系统控件,并进行了优化以确保对滚动性能的影响最小。特征允许对UIScrollView的整个内容高度进行细粒度的滚动。 通过Objective-C运行时和KVO直接与UIScrollView互操作。 与标准...

    react-storefront:React Storefront-电子商务的PWA。 100%离线,与平台无关,无头,支持Magento 2。 始终开源,Apache-2.0许可证。 加入我们作为贡献者(contributors@reactstorefront.io)

    React Storefront框架...可笑的速度React Storefront付出了更多努力,以从所有可能的实际和用户感知的性能优化中挤出速度,包括: 动态数据的高速缓存命中率高服务器端渲染自动创建AMP 动态数据的预测性预取代码拆分缓

    4P与4C营销理论企业应用方案.docx

    假如企业不管4C只是、一味地强4调4P理论,那就是在闭门造车,一定会制订出可笑的销售政策、可笑的产品、可笑的促销计划;假如企业只是、一味地站在消费者的角度进行4C的时候,来满意消费者的需求,企业的成本将会...

    RecyclerView5.1.1后的各个版本

    例如,它提供了更强大的性能优化,如回收视图(view recycling)机制,减少内存占用和提高滚动流畅性。同时,RecyclerView支持多种布局管理器,如线性布局(LinearLayoutManager)、网格布局(GridLayoutManager)和...

    comic:测试

    描述中的"可笑的"可能是在描述漫画内容的幽默性质,或者是在开发过程中遇到了一些令人啼笑皆非的问题。结合标签"JavaScript",我们可以推测这是一个使用JavaScript技术来创建的Web应用,可能是用于展示漫画、制作...

    comic

    在描述中提到的"可笑的",虽然在中文里主要表达一种幽默或滑稽的感觉,但在IT语境下,这可能是对设计或交互性的形容,比如一个具有趣味性、引人发笑的漫画网站或应用。这可能涉及到如何通过HTML的特性,如CSS...

    雅思写作分类词汇.doc

    38. ridiculous(可笑的):批评某些科技观念或应用的荒谬之处。 39. absurd(荒唐的):形容不切实际或无意义的科技项目。 40. substitute(取代):新的科技可能替代旧的技术。 41. overcome difficulties(克服...

Global site tag (gtag.js) - Google Analytics