xml地图|网站地图|网站标签 [设为首页] [加入收藏]
字符串的格式化,编译器优化那点事
分类:编程

使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的。
优化代码开关即optimize开关,和debug开关一起,有以下几种组合。
图片 1

python字符串的格式化分为两种:1)% 方式    2)str.format()  方式。

 

在Visual Sutdio中新建一个C#项目时,
项目的“调试”(Debug)配置的是/optimize-和/debug:full开关,
而“发布”(Release)配置指定的是/optimize+和/debug:pdbonly开关

str.format() 是比 % 较新的方式, 大多数的 Python 代码仍然使用 % 操作符。但最终会被str.format()代替,推荐使用str.format()

#网络爬虫之最基本的爬虫:爬取[网易新闻排行榜](http://news.163.com/rank/)

**一些说明:** 

* 使用urllib2或requests包来爬取页面。

* 使用正则表达式分析一级页面,使用Xpath来分析二级页面。

* 将得到的标题和链接,保存为本地文件。

import os
import sys
import requests
import re

from lxml import etree


def StringListSave(save_path, filename, slist):
    # 检测是否有这个文件目录,不存在的话,会自动创建
    if not os.path.exists(save_path):
        os.makedirs(save_path)
    path = save_path+"/"+filename+".txt"
    with open(path, "w+") as fp:
        for s in slist:
            # 做了utf8转码,转为终端可识别的码制
            fp.write("%stt%sn" % (s[0].encode("utf8").decode('utf-8'), s[1].encode("utf8").decode('utf-8')))


def Page_Info(myPage):
    '''Regex'''
    # 这里的re.findall 返回的是一个元组列表,内容是 (.*?) 中匹配到的内容
    # 析取每个链接的标题和链接
    mypage_Info = re.findall(r'<div class="titleBar" id=".*?"><h2>(.*?)'
                             r'</h2><div class="more"><a href="(.*?)">.*?</a></div></div>', myPage, re.S)
    return mypage_Info

def New_Page_Info(new_page):
    '''Regex(slowly) or Xpath(fast)'''
    # 将new_page的内容转为html格式的树
    dom = etree.HTML(new_page)
    # 析取 <tr <td <a中的文本
    new_items = dom.xpath('//tr/td/a/text()')
    # 析取 <tr <td <a中的链接, @href 是一个属性
    new_urls = dom.xpath('//tr/td/a/@href')
    assert(len(new_items) == len(new_urls))
    return zip(new_items, new_urls)

def Spider(url):
    i = 0
    print("downloading ", url)
    myPage = requests.get(url).content.decode("gbk")
    myPageResults = Page_Info(myPage)
    save_path = "网易新闻抓取"
    filename = str(i)+"_"+"新闻排行榜"
    StringListSave(save_path, filename, myPageResults)
    i += 1
    for item, url in myPageResults:
        print("downloading ", url)
        new_page = requests.get(url).content.decode("gbk")
        newPageResults = New_Page_Info(new_page)
        filename = str(i)+"_"+item
        StringListSave(save_path, filename, newPageResults)
        i += 1


if __name__ == '__main__':
    print("start")
    start_url = "http://news.163.com/rank/"
    Spider(start_url)
    print("end")

optimize-/+决定了编译器是否优化代码,optimize-就是不优化了,但是通常,有一些基本的“优化”工作,无论是否指定optimize+,都会执行。

1、% 方式

 

optimize- and optimize+

该项功能主要用于动态语义分析,帮助我们更好地编写代码。

1)用法

解析一:检测是否有这个文件目录,不存在的话,会自动创建

- 常量计算

在写程序的时候,有时能看见代码下面划了一道红波浪线,那就是编译器动态检查。常量计算,就是这样,编译器会计算常量,帮助判断其他错误。  
![](https://images2018.cnblogs.com/blog/616093/201805/616093-20180501011736735-1016278688.png)

%[(name)][flags][width].[precision]typecode

import os
save_path = "网易新闻抓取"
if not os.path.exists(save_path):
   os.makedirs(save_path)

- 简单分支检查

如果swtich写了两个以上的相同条件,或者分支明显无法访问到,都会弹出提示。  
![](https://images2018.cnblogs.com/blog/616093/201805/616093-20180501011749912-993405991.png)

2)说明

 

- 未使用变量

不多说明,直接看图。  
![](https://images2018.cnblogs.com/blog/616093/201805/616093-20180501012150677-566406144.png)

(name)     可选,用于选择指定的key
flags      可选,主要用于数字类型
width        可选,显示宽度,width大于显示的长度用空格填充,小于则原样输出
.precision      可选,,显示长度,字符类型:.precision大于显示长度原样输出,小于则显示前precision位字符。数字类型:小数点后保留的位数
typecode    必选,字符串格式化符号

解析二:os.mkdir 与 os.makedirs 区别及用法:

- 使用未赋值变量

不多说,看图。  
![](https://images2018.cnblogs.com/blog/616093/201805/616093-20180501011804349-1464238631.png)

flags 的选项有:

(1)mkdir( path [,mode] )

局限

使用变量参与计算,随便写一个算式,就可以绕过一些检查,虽然我们看来是明显有问题的。
图片 2

+     右对齐;在正数前面显示加号“+”

      作用:创建一个目录,可以是相对或者绝对路径,mode的默认模式是0777。
      如果目录有多级,则创建最后一级。如果最后一级目录的上级目录有不存在的,则会抛出一个OSError。

optimize+ only

首先需要了解c#代码编译的过程,如下图:
图片 3
图片来自

C# compiler将C#代码生成IL代码的就是所谓的编译器优化。先说重点。
.NET的JIT机制,主要优化在JIT中完成,编译器optimize只做一点简单的工作。(划重点)

探究一下到底干了点啥吧,以下是使用到的工具。

Tools:
Visual studio 2017 community targeting .net core 2.0
IL DASM(vs自带)

  •      左对齐
    <sp>    右对齐;正数前用空格填充
    0     显示的数字前面填充'0'
    #     在八进制数前面显示零('0'),在十六进制前面显示'0x'或者'0X'(取决于用的是%x或%X)

(2)makedirs( path [,mode] )

使用IL DASM可以查看编译器生成的IL代码,这样就能看到优化的作用了。IL代码的用途与机制不是本文的重点,不明白的同学可以先去看看《C# via CLR》(好书推荐)。

按照优化的类型进行了简单的分类。

typecode 符串格式化符号: 

      作用: 创建递归的目录树,可以是相对或者绝对路径。
      如果子目录创建失败或者已经存在,会抛出一个OSError的异常,Windows上Error      183即为目录已经存在的异常错误。如果path只有一级,与mkdir一样。

- 从未使用变量

代码如下:
using System;
using System.Threading.Tasks;

namespace CompileOpt
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 3;
            Console.WriteLine("sg");
        }
    }
}

未优化的时候

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       15 (0xf)
  .maxstack  1
  .locals init (int32 V_0)
  IL_0000:  nop
  IL_0001:  ldc.i4.3
  IL_0002:  stloc.0
  IL_0003:  ldstr      "sg"
  IL_0008:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000d:  nop
  IL_000e:  ret
} // end of method Program::Main

使用优化开关优化之后:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       11 (0xb)
  .maxstack  8
  IL_0000:  ldstr      "sg"
  IL_0005:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000a:  ret
} // end of method Program::Main

.locals init (int32 V_0)消失了(局部变量,类型为int32)
ldc.i4.3(将3推送到堆栈上)和stloc.0(将值从堆栈弹出到局部变量 0)也消失了。
所以,整个没有使用的变量,在设置为优化的时候,就直接消失了,就像从来没有写过一样。

c   格式化字符及其ASCII码
s   格式化字符串
d   格式化整数
u   格式化无符号整型
o   格式化无符号八进制数
x   格式化无符号十六进制数
X   格式化无符号十六进制数(大写)
f    格式化浮点数字,可指定小数点后的精度(默认保留6位小数)
e   用科学计数法格式化浮点数
E   作用同%e,用科学计数法格式化浮点数
g   %f和%e的简写
G   %f和%E的简写
p   十六进制数格式化变量的地址
%  显示一个 "%"

     总结:os.mkdir()创建路径中的最后一级目录;os.makedirs()创建多层目录。

- 空try catch语句

代码如下:
using System;
using System.Threading.Tasks;

namespace CompileOpt
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {

            }
            catch (Exception)
            {
                Console.WriteLine(DateTime.Now);
            }

            try
            {

            }
            catch (Exception)
            {
                Console.WriteLine(DateTime.Now);

            }
            finally
            {
                Console.WriteLine(DateTime.Now);

            }
        }
    }
}

未优化

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       74 (0x4a)
  .maxstack  1
  IL_0000:  nop
  .try
  {
    IL_0001:  nop
    IL_0002:  nop
    IL_0003:  leave.s    IL_001a
  }  // end .try
  catch [System.Runtime]System.Exception 
  {
    IL_0005:  pop
    IL_0006:  nop
    IL_0007:  call       valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()
    IL_000c:  box        [System.Runtime]System.DateTime
    IL_0011:  call       void [System.Console]System.Console::WriteLine(object)
    IL_0016:  nop
    IL_0017:  nop
    IL_0018:  leave.s    IL_001a
  }  // end handler
  IL_001a:  nop
  .try
  {
    .try
    {
      IL_001b:  nop
      IL_001c:  nop
      IL_001d:  leave.s    IL_0034
    }  // end .try
    catch [System.Runtime]System.Exception 
    {
      IL_001f:  pop
      IL_0020:  nop
      IL_0021:  call       valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()
      IL_0026:  box        [System.Runtime]System.DateTime
      IL_002b:  call       void [System.Console]System.Console::WriteLine(object)
      IL_0030:  nop
      IL_0031:  nop
      IL_0032:  leave.s    IL_0034
    }  // end handler
    IL_0034:  leave.s    IL_0049
  }  // end .try
  finally
  {
    IL_0036:  nop
    IL_0037:  call       valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()
    IL_003c:  box        [System.Runtime]System.DateTime
    IL_0041:  call       void [System.Console]System.Console::WriteLine(object)
    IL_0046:  nop
    IL_0047:  nop
    IL_0048:  endfinally
  }  // end handler
  IL_0049:  ret
} // end of method Program::Main

优化开关开启:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       19 (0x13)
  .maxstack  1
  .try
  {
    IL_0000:  leave.s    IL_0012
  }  // end .try
  finally
  {
    IL_0002:  call       valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()
    IL_0007:  box        [System.Runtime]System.DateTime
    IL_000c:  call       void [System.Console]System.Console::WriteLine(object)
    IL_0011:  endfinally
  }  // end handler
  IL_0012:  ret
} // end of method Program::Main

很明显可以看到,空的try catch直接消失了,但是空的try catch finally代码是不会消失的,但是也不会直接调用finally内的代码(即还是会生成try代码段)。

3)实例

 

- 分支简化

代码如下:
using System;
using System.Threading.Tasks;

namespace CompileOpt
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 3;
            if (x == 3)
                goto LABEL1;
            else
                goto LABEL2;
            LABEL2: return;
            LABEL1: return;
        }
    }
}

未优化的情况下:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       22 (0x16)
  .maxstack  2
  .locals init (int32 V_0,
           bool V_1)
  IL_0000:  nop
  IL_0001:  ldc.i4.3
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  ldc.i4.3
  IL_0005:  ceq
  IL_0007:  stloc.1
  IL_0008:  ldloc.1
  IL_0009:  brfalse.s  IL_000d
  IL_000b:  br.s       IL_0012
  IL_000d:  br.s       IL_000f
  IL_000f:  nop
  IL_0010:  br.s       IL_0015
  IL_0012:  nop
  IL_0013:  br.s       IL_0015
  IL_0015:  ret
} // end of method Program::Main

优化:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       5 (0x5)
  .maxstack  8
  IL_0000:  ldc.i4.3
  IL_0001:  ldc.i4.3
  IL_0002:  pop
  IL_0003:  pop
  IL_0004:  ret
} // end of method Program::Main

优化的情况下,一些分支会被简化,使得调用更加简洁。

>>> "hello %s" % "world"
'hello world'
>>> "%s %d" % ("hello world", 2018)
'hello world 2018'
>>> "hello %10.2s" % "world"     #"world"的显示宽度 10,只显示两位字符
'hello         wo'
>>> "%(name)s, %(age)d" % {"name":"LaoWang", "age":18}     # 通过(name)指定显示的内容
'LaoWang, 18'
>>> "%.2f" % 123.456    # 数字类型,保留两位小数(默认四舍五入)
'123.46'
>>> "%(num).2f" % {"num":123.456}
'123.46'

解析三:文件操作,with open as追加文本内容实例:

- 跳转简化

代码如下:
using System;
using System.Threading.Tasks;

namespace CompileOpt
{
    class Program
    {
        static void Main(string[] args)
        {
            goto LABEL1;
            LABEL2: Console.WriteLine("234");
            Console.WriteLine("123");
            return;
            LABEL1: goto LABEL2;
        }     
    }
}

未优化:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       32 (0x20)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  br.s       IL_001c
  IL_0003:  nop
  IL_0004:  ldstr      "234"
  IL_0009:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000e:  nop
  IL_000f:  ldstr      "123"
  IL_0014:  call       void [System.Console]System.Console::WriteLine(string)
  IL_0019:  nop
  IL_001a:  br.s       IL_001f
  IL_001c:  nop
  IL_001d:  br.s       IL_0003
  IL_001f:  ret
} // end of method Program::Main

优化后:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       21 (0x15)
  .maxstack  8
  IL_0000:  ldstr      "234"
  IL_0005:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000a:  ldstr      "123"
  IL_000f:  call       void [System.Console]System.Console::WriteLine(string)
  IL_0014:  ret
} // end of method Program::Main

一些多层的标签跳转会得到简化,优化器就是人狠话不多。

 

(1) 最常见的读写操作

- 临时变量消除

一些临时变量(中间变量)会被简化消除。代码如下:
using System;
using System.Threading.Tasks;

namespace CompileOpt
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine(i);
            }
            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine(i + 1);
            }
        }
    }
}

只显示最关键的变量声明部分,未优化的代码如下:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       54 (0x36)
  .maxstack  2
  .locals init (int32 V_0,
           bool V_1,
           int32 V_2,
           bool V_3)
  IL_0000:  nop

优化后:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       39 (0x27)
  .maxstack  2
  .locals init (int32 V_0,
           int32 V_1)
  IL_0000:  ldc.i4.0

很显然,中间的bool型比较变量消失了。

2、str.format()  方式    (可参考PEP 3101)

import re
with open('/rr.txt', 'w') as f:
   f.write('hello world')

- 空指令删除

看第一个例子,很明显,代码中没有了nop字段,程序更加紧凑了。

编译器版本不同,对应的优化手段也不尽相同,以上只列出了一些,应该还有一些没有讲到的,欢迎补充。

1)、用法

如图所示:

延伸阅读:.NET中的优化(转载自)

在.NET的编译模型中没有链接器。但是有一个源代码编译器(C# compiler)和即时编译器(JIT compiler),源代码编译器只进行很小的一部分优化。比如它不会执行函数内联和循环优化。

从优化能力上来讲RyuJIT和Visual C++有什么不同呢?因为RyuJIT是在运行时完成其工作的,所以它可以完成一些Visual C++不能完成的工作。比如在运行时,RyuJIT可能会判定,在这次程序的运行中一个if语句的条件永远不会为true,所以就可以将它移除。RyuJIT也可以利用他所运行的处理器的能力。比如如果处理器支持SSE4.1,即时编译器就会只写出sumOfCubes函数的SSE4.1指令,让生成打的代码更加紧凑。但是它不能花更多的时间来优化代码,因为即时编译所花的时间会影响到程序的性能。

在当前控制托管代码的能力是很有限的。C#和VB编译器只允许使用/optimize编译器开关打开或者关闭优化功能。为了控制即时编译优化,你可以在方法上使用System.Runtime.Compiler­Services.MethodImpl属性和MethodImplOptions中指定的选项。NoOptimization选项可以关闭优化,NoInlining阻止方法被内联,AggressiveInlining (.NET 4.5)选项推荐(不仅仅是提示)即时编译器将一个方法内联。

:[[fill]align][sign][#][0][minimumwidth][.precision][type]

 图片 4

结语

话说整点这个东西有点什么用呢?
要说是有助于更好理解.NET的运行机制会不会有人打我...
说点实际的,有的童鞋在写延时程序时,timer.Interval = 10 * 60 * 1000,作为强迫症患者,生怕这么写不好,影响程序执行。但是,这种写法完全不会对程序的执行有任何影响,我认为还应该推荐,因为增加了程序的可读性,上面的代码段就是简单的10分钟,一看就明白,要是算出来反而可读性差。另外,分支简化也有助于我们专心依照业务逻辑去编写代码,而不需要过多考虑代码的分支问题。其他的用途各位看官自行发挥啦。

2)、说明

追加写入文件内容:

 

import re
with open('/rr.txt', 'a') as f:
    f.write('hello worldn')
    # print(f.readline(1))

本文由澳门新葡亰手机版发布于编程,转载请注明出处:字符串的格式化,编译器优化那点事

上一篇:自学成才之路,使用else代替状态变量 下一篇:没有了
猜你喜欢
热门排行
精彩图文