找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 2142|回复: 6

【C Python Win32ASM】编码问题与速度对比

[复制链接]
发表于 2020-4-2 18:16:04 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
本帖最后由 watermelon 于 2020-4-4 16:27 编辑

最近老师对我的Python代码中对try/except来判断文件编码的操作产生了疑问,问我这个是什么意思?所以要求我写一个文档来解释一下并且对比一下python和C的速度问题。
1. python编码问题
1.1文件读写方式;
     一般来说,文件读写有两种常见的方式;
     一种是硬盘I/O,一种是文件的内存映射。
  其中,文件的硬盘读写是最为常见的文件读写方式,一般程序语言的默认教程中举例均为文件的硬盘读写方式,其优点是编写简单,考虑的因素比较少,缺点是速度太慢,当读写大体积文件的时候容易使程序卡死而进入假死状态
(无响应)。
     第二种文件的内存映射(file mapping)是文件读写的一种高级方式,其不再使用硬盘进而使用内存来进行文件数据的交换。将硬盘上文件的数据映射到内存中,由于内存的数据传输速度比硬盘的数据传输速度要高很多,所以文件的内存映射会非常的快,处理大数据文件不需要考虑速度问题,但是需要考虑其他的一些问题:如Windows系统上的内存分页,内存分配机制的问题。内存映射的不足之处在于文件映射无法创建新文件(即无法映射空文件);同时文件映射只能修改,编写与源文件中数据长度相等的字符串,所以在添加与源文件长度不相符的内容的时候,应该应用常规的硬盘文件读写方式;最后要注意的一点是文件映射读写的数据是二进制格式的数据,需要对其编码进行判断,转换成我们熟知的常规字符串才可以字符串操作。
1.2 二进制数据与文本数据;
     二进制数据是由二进制字符串组成,如利用记事本打开一个exe文件,会看到由于二进制文件与文本文件互不相通的问题而产生的乱码,此时需要用专门的二进制文件查看工具(如WinHex)来查看二进制文件。
     文本数据是我们常见的可以用记事本打开的文件数据,其字符串是常见的“abcdefg”这类的我们读得懂的字符来显示的。
     总体来说,二进制文件用于计算机的读写,文本文件用于方便人类的读写。
1.3 文件的编码问题
     由于二进制文件与文本文件不是一个类型的文件,所以需要一种转换模式可以帮助二进制文件与文本文件之间自由的转换,而这个转换的规则就是文件的编码。一般来说,我们可能会见到过ASCII格式、UTF-8格式、GBK格式、GB2312格式等类型的编码。
     文本文件给计算机传递信息,计算机只知道01010101二进制,所以需要将我们的abcedf编码(encode)成0101010给计算机认识;当我们想知道计算机干什么的时候,由于计算机是01010101,所以需要将00101010101的二进制数据解码(decode)成常见的字符串abcdef来给我们读写。
     由于python的文件映射出来的数据为二进制,并且如果编码/解码格式选取不正确可能会引发异常,所以可能需要异常处理结构来处理编码的问题,这种情况需要具体问题具体分析。
2.CPython与C在文件映射读取方面的速度比较;
     经过编写代码与实验,在循环100次文件映射读取的基础上,CPython代码的耗时为0.024秒左右;使用Windows API编写的在Windows平台上的C语言程序耗时基本为0.054秒,使用Linux上的文件映射mmap编写的程序在编译命令gcc filemap.c –o filemap.o下编译完成后,程序耗时约为0.005秒(比python快了将近5倍)。

实验的程序:
Cpython代码:
  1. # coding:utf-8

  2. # Purpose: Try to compare the speed of C and CPython code.

  3. import time
  4. import mmap
  5. import os
  6. import os.path

  7. # Get the file size,
  8. # if file is empty, then it can not be file-mapped.
  9. def CheckFileEmpty(filename):
  10.     return os.path.getsize(filename)


  11. def MapFile(filename):
  12.     with open(filename, 'rb') as f:
  13.         mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
  14.         bytes_content = mm.read()
  15.         mm.close()
  16.    
  17.     # Decode the bytes string.
  18.     try:
  19.         content = bytes_content.decode('utf-8')
  20.     except:
  21.         content = bytes_content.decode('GB2312')
  22.    
  23.     # Output the file content.
  24.     #print(content)

  25. def main():
  26.    
  27.     # Start time.
  28.     t1 = time.time()
  29.    
  30.     # Begin the 100 cycles.
  31.     for i in range(100):
  32.         # Check the file size.
  33.         if CheckFileEmpty('temp.txt') == 0:
  34.             print('The file size if zero, please choose another file.')
  35.             return
  36.         
  37.         # Begin to file mapping.
  38.         MapFile('temp.txt')
  39.    
  40.     t2 = time.time()
  41.    
  42.     print('Time consuming: ', str(t2-t1)[:9], 's')

  43. if __name__ == '__main__':
  44.     main()
复制代码


Linux上C语言:

  1. // This program's time cosumption is 0.005s

  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <fcntl.h>
  6. #include <sys/stat.h>
  7. #include <string.h>
  8. #include <sys/mman.h>
  9. #include <time.h>
  10. #include <sys/time.h>

  11. #ifndef BUF_SIZE
  12. #define BUF_SIZE 4096
  13. #endif


  14. void FileMap(const char *filename)
  15. {
  16.         // File descriptor.
  17.         int fp;

  18.         // Set the file attributes structure.
  19.         struct stat st;
  20.        
  21.         //Open the file.
  22.         fp = open(filename, O_RDWR);

  23.         if(fp == -1)
  24.         {
  25.                 printf("[ * ] Can't open the file...\n");
  26.                 printf("[ * ] Maybe the file access is denied...\n");
  27.                 return ;
  28.         }


  29.         // Check the file size.
  30.         fstat(fp, &st);
  31.         if(st.st_size == 0)
  32.         {
  33.                 printf("[ * ] Can't make the empty file to the file mapping...\n");
  34.                 goto label2;
  35.         }

  36.         // Set the pointer of mmap return,
  37.         // which is also the address of the file content.
  38.         char *buffer = (char*)malloc(sizeof(char)*BUF_SIZE);
  39.         // Record the buffer address.
  40.         // because, when use the mmap, buffer address has been changed.
  41.         char *ptr = buffer;

  42.         if(buffer == NULL)
  43.         {
  44.                 printf("[ * ] Memory allocate is failed...\n");
  45.                 goto label2;
  46.         }

  47.         buffer = mmap(NULL, BUF_SIZE, PROT_READ, MAP_SHARED, fp, 0);
  48.         if(buffer == NULL)
  49.         {
  50.                 printf("[ * ] File mapping has error...\n");
  51.                 goto label1;
  52.         }

  53.         //printf("[ * ]The file content is :\n%s\n", buffer);

  54. label1:
  55.         free(ptr);
  56. label2:
  57.         close(fp);
  58.         //printf("[ * ] All done!\n");
  59.         return ;
  60. }




  61. int main(int argc, char *argv[])
  62. {
  63.         struct timeval starttime;
  64.         struct timeval endtime;
  65.         double timeuse = 0;
  66.        
  67.         gettimeofday(&starttime, NULL);
  68.         // 100 cycles;
  69.         for(int i = 0;i<100;i++)
  70.                 FileMap("hello.txt");
  71.         gettimeofday(&endtime, NULL);

  72.         timeuse = 1000000 * (endtime.tv_sec - starttime.tv_sec) +
  73.                 endtime.tv_usec - starttime.tv_usec;

  74.         timeuse = timeuse / 1000000;
  75.         printf("Time use=%lfs\n", timeuse);

  76.         return 0;
  77. }

  78.        
复制代码


利用Windows API 编写的文件映射读取的C语言程序:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <Windows.h>

  4. #ifndef BUF_SIZE
  5. #define BUF_SIZE 0 // Used to map the whole file.
  6. #endif

  7. // The file mapping can not manipulate the empty file,
  8. // so we should check the file is empty or not.
  9. DWORD CheckFileEmpty(CHAR *filename)
  10. {
  11.         // Initialize the file attribute information structure.
  12.         WIN32_FILE_ATTRIBUTE_DATA ws32 = { 0 };

  13.         BOOL status = GetFileAttributesExA(filename,
  14.                 GetFileExInfoStandard,
  15.                 (LPVOID)&ws32);
  16.        
  17.         if (status == FALSE)
  18.         {
  19.                 printf("GetFileAttributesExA has a error, the code is: %d\n", GetLastError());
  20.                 return 0;
  21.         }

  22.         // Return the file size.
  23.         // Because the file size is very small,
  24.         // so, return the low part is OK.
  25.         return ws32.nFileSizeLow;

  26. }


  27. // Use the file mapping to get the content of target file.
  28. VOID FileMapping(CHAR *filename)
  29. {
  30.         // Get the file open handle.
  31.         HANDLE hFile = CreateFileA(filename,
  32.                 GENERIC_READ | GENERIC_WRITE,
  33.                 0,        // Exclusive this process to manipulate the file.
  34.                 NULL,
  35.                 OPEN_ALWAYS,
  36.                 FILE_ATTRIBUTE_NORMAL,
  37.                 NULL);
  38.         if (hFile == INVALID_HANDLE_VALUE)
  39.         {
  40.                 printf("CreateFileA has error, the code is: %d\n", GetLastError());
  41.                 return;
  42.         }

  43.         // Create a file mapping object.
  44.         HANDLE hFileMap = CreateFileMappingA(hFile,
  45.                 NULL,
  46.                 PAGE_READWRITE,        // We can read and write to this file map view.
  47.                 0,                               
  48.                 BUF_SIZE,                // Get the BUF_SIZE length of data.
  49.                 filename);
  50.         if (hFileMap == INVALID_HANDLE_VALUE)
  51.         {
  52.                 printf("CreateFileMappingA has error, the code is:%d\n", GetLastError());
  53.                 goto label2;
  54.         }

  55.         // Begin to get the content of this file mapping.
  56.         LPVOID lpFileMap = MapViewOfFile(hFileMap,
  57.                 FILE_MAP_READ,
  58.                 0,
  59.                 0,
  60.                 // This parameter should be the same with CreateFileMappingA
  61.                 // which has the specific data length.
  62.                 BUF_SIZE);

  63.         if (lpFileMap == NULL)
  64.         {
  65.                 printf("MapViewOfFile has error, the code is:%d\n", GetLastError());
  66.                 goto label1;
  67.         }

  68.         //printf("The file content:%s\n", lpFileMap);


  69. label1:
  70.         CloseHandle(hFileMap);
  71.         //printf("Close handle: hFileMap.\n");
  72. label2:
  73.         CloseHandle(hFile);
  74.         //printf("Close handle: hFile.\n");
  75.         UnmapViewOfFile((LPCVOID)lpFileMap);
  76.         //printf("Unmap the file: lpFileMap.\n");
  77.         return;
  78. }


  79. int main(int argc, char *argv[])
  80. {
  81.         // Begin to count the program running time.
  82.         LARGE_INTEGER foreTime = { 0 };
  83.         LARGE_INTEGER backTime = { 0 };
  84.         LARGE_INTEGER freq = { 0 };
  85.        
  86.         // Used to set the specific threads have connection to
  87.         // which CPU cores.
  88.         QueryPerformanceFrequency(&freq);
  89.         QueryPerformanceCounter(&foreTime);

  90.         // Cycle the program 100 times.
  91.         for (int i = 0; i < 100; i++)
  92.         {
  93.                 DWORD dwFileSize = 0;
  94.                 if ((dwFileSize = CheckFileEmpty("test.txt")) == 0)
  95.                 {
  96.                         printf("The empty file can not be file-mapped.\n");
  97.                         return 0;
  98.                 }

  99.                 //printf("The file size is : %d bytes.\n", dwFileSize);

  100.                 // Begin the file mapping.
  101.                 FileMapping("test.txt");
  102.         }

  103.         QueryPerformanceCounter(&backTime);

  104.         printf("The time cosumption is : %fs\n",
  105.                 (FLOAT)(backTime.QuadPart - foreTime.QuadPart) / (FLOAT)freq.QuadPart);

  106.         return 0;
  107. }
复制代码


实验文本:temp.txt/test.txt/hello.txt
  1. Hello world!
复制代码


Win32ASM:本程序在获取文件属性上得到了群大佬GodOfHack与QQ小冰GUID666.....的帮助,表示感谢!
  1. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  2. ; 文件映射 FILE MAPPING
  3. ; 程序来自:[url]www.0xAA55.com[/url]
  4. ; 该程序遵循较为宽松的MIT许可
  5. ; Copyright @ watermelon/ZXG/trace-shadow
  6. ; 如果有什么不足的地方请联系我:[email]starlight_chou@163.com[/email]
  7. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  8.                 .386
  9.                 .model        flat, stdcall
  10.                 option        casemap:none

  11. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  12. ; include 文件
  13. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  14. include                windows.inc
  15. include                kernel32.inc
  16. includelib        kernel32.lib
  17. include                user32.inc
  18. includelib        user32.lib


  19. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  20. ; 数据段
  21. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  22.                 .data
  23. szMsgCaption        db        'Information', 0
  24. szFileName        db        'hello.txt', 0
  25. szError                db        'Error', 0
  26. szErrorCode        db        'Error code is: %d', 0
  27. szEmptyFile        db        'This is an empty file!', 0
  28. szFormat        db        '%d μs', 0
  29. ; 用于记录程序运行时间的相关变量
  30. dqForeTime        dq        0
  31. dqBackTime        dq        0
  32. dqFreq                dq        0
  33. dqTime                dq        0
  34. dwlm                dd        1000000



  35. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  36. ; 代码段
  37. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  38.                 .code

  39. ; 判断文件是否为空文件
  40. ; 返回值:文件的大小
  41. ; 注意:本程序在GetFileAttributesEx中的第二个参数上受到了
  42. ; 群大佬GodOfHacker与QQ小冰[GUID:6666...]的指导,小弟在此表示感谢。
  43. _CheckEmptyFile        proc        uses ecx
  44.                         LOCAL        @ws32:WIN32_FILE_ATTRIBUTE_DATA
  45.                         LOCAL        @errorCode:dword
  46.                        
  47.                         ; 初始化结构体,全部填零。
  48.                         invoke        RtlZeroMemory, addr @ws32, sizeof WIN32_FILE_ATTRIBUTE_DATA
  49.                        
  50.                         ; 获取文件属性
  51.                         ; 这里第二个参数应该填GetFileExInfoStandard
  52.                         ; 由于masm32中没有这个参数
  53.                         ; 但是由于本身这个参数位于枚举类型GET_FILEEX_INFO_LEVELS中
  54.                         ; 定义如下
  55.                         ; typedef enum _GET_FILEEX_INFO_LEVELS {
  56.                           ;                        GetFileExInfoStandard,
  57.                           ;                        GetFileExMaxInfoLevel
  58.                         ;                } GET_FILEEX_INFO_LEVELS;
  59.                         ; 依据枚举类型中的知识,每一个元素默认从0号开始往后排
  60.                         ; 所以枚举里面的第一个元素为0
  61.                         invoke        GetFileAttributesEx, offset szFileName, \
  62.                                 0, \
  63.                                 addr @ws32
  64.                        
  65.                         .if        eax == FALSE
  66.                                 ; 获取last error
  67.                                 call        GetLastError
  68.                                 invoke        wsprintf, addr @errorCode, offset szErrorCode, eax
  69.                                 invoke        MessageBox, NULL, addr @errorCode, offset szError, MB_OK
  70.                                 xor        eax, eax
  71.                                 ret
  72.                         .endif
  73.                        
  74.                         ; 由于此处测试的文件不会大于4G,所以就返回低位的数值了。
  75.                         mov        eax, @ws32.nFileSizeLow
  76.         ret

  77. _CheckEmptyFile endp

  78. ; 文件映射
  79. ; 无返回值
  80. _FileMapping        proc        uses ecx
  81.                 LOCAL        @hFile:dword
  82.                 LOCAL        @hFileMap:dword
  83.                 LOCAL        @errorCode:dword
  84.                 ; 存储文件映射读取出来的内容的首地址
  85.                 LOCAL         @content_addr:dword
  86.                 LOCAL        @mapViewofFile
  87.                
  88.                 ; 打开文件
  89.                 invoke        CreateFile, offset szFileName, GENERIC_READ or GENERIC_WRITE, \
  90.                         0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL

  91.                 mov        @hFile, eax
  92.                
  93.                
  94.                 .if        eax == INVALID_HANDLE_VALUE
  95.                 @@:
  96.                         ; 获取last error
  97.                         call        GetLastError
  98.                         invoke        wsprintf, addr @errorCode, offset szErrorCode, eax
  99.                         invoke        MessageBox, NULL, addr @errorCode, offset szError, MB_OK
  100.                         xor        eax, eax
  101.                         ret
  102.                 .endif
  103.                
  104.                 ; 创建一个文件映射对象
  105.                 invoke        CreateFileMapping, @hFile, NULL, \
  106.                         PAGE_READWRITE, 0, 0, offset szFileName
  107.                
  108.                 mov        @hFileMap, eax
  109.                
  110.                 .if        eax == INVALID_HANDLE_VALUE
  111.                         invoke        CloseHandle, @hFile
  112.                         jmp        @B
  113.                 .endif
  114.                
  115.                 ; 获取文件内容
  116.                 invoke        MapViewOfFile, @hFileMap, \
  117.                         FILE_MAP_READ, 0, 0, 0
  118.                
  119.                 .if        eax == NULL
  120.                         invoke        CloseHandle, @hFile
  121.                         invoke        CloseHandle, @hFileMap
  122.                         jmp        @B
  123.                 .endif
  124.                
  125.                 mov        @content_addr, eax
  126.                 ; 显示文件映射的内容
  127.                 ;invoke        MessageBox, NULL, @content_addr, offset szMsgCaption, MB_OK
  128.                
  129.                 ; 关闭句柄
  130.                 invoke        CloseHandle, @hFile
  131.                 invoke        CloseHandle, @hFileMap
  132.                 invoke        UnmapViewOfFile, @mapViewofFile
  133.        
  134.         xor        eax, eax
  135.         ret
  136. _FileMapping endp


  137. ; 计时函数
  138. ; 返回值:无
  139. _TimeConsumption        proc
  140.                         ; 用于记录输出时间的格式化字符串地址
  141.                         LOCAL        @time_addr:dword
  142.                
  143.                         ; 开始计时
  144.                         invoke        QueryPerformanceFrequency, offset dqFreq
  145.                         invoke        QueryPerformanceCounter, offset dqForeTime

  146.                         ; 循环100次文件映射的过程
  147.                         mov        ecx, 100
  148.                         @@:
  149.                                 call        _CheckEmptyFile
  150.                                 .if        eax == 0
  151.                                         invoke        MessageBox, NULL, offset szEmptyFile, offset szMsgCaption, MB_OK
  152.                                         ret
  153.                                 .else
  154.                                         call        _FileMapping
  155.                                 .endif
  156.                         loop        @B

  157.                         ; 结束计时
  158.                         invoke        QueryPerformanceCounter, offset dqBackTime
  159.                        
  160.                         ; 处理浮点数,参考罗云彬《Windows环境下32位汇编语言程序设计》
  161.                         mov        eax, dword ptr dqForeTime
  162.                         mov        edx, dword ptr dqForeTime + 4
  163.                         sub        dword ptr dqBackTime, eax
  164.                         sbb        dword ptr dqBackTime + 4, edx
  165.                        
  166.                         finit
  167.                         fild        dqFreq
  168.                         fild        dqBackTime
  169.                         fimul        dwlm
  170.                         fdivr
  171.                         fistp        dqTime ; μs
  172.                        
  173.                         invoke        wsprintf, addr @time_addr, offset szFormat, dqTime
  174.                         invoke        MessageBox, NULL, addr @time_addr, offset szMsgCaption, MB_OK
  175.                
  176.                
  177.                 xor        eax, eax
  178.                 ret
  179. _TimeConsumption endp



  180. ; 主程序
  181. start:
  182.                 call        _TimeConsumption
  183.                 invoke        ExitProcess, NULL

  184. end start

复制代码

运行得到的时间和Win32API写的写语言的release版本时间差不多,也是0.05秒左右
2020-04-04_123550.png

小弟对于程序的优化问题不是特别懂,希望各位大佬对本文看到有什么问题后直接留言批评我!

实验环境:
Windows: visual studio2013, 32位程序
Cpython:version 3.8.1 64位
Linux上:GCC 8.3.0
汇编器:MASM32
回复

使用道具 举报

发表于 2020-4-12 14:05:07 | 显示全部楼层
这个速度比起来意义不大啊。整个费时的比较才有效。
回复 赞! 1 靠! 0

使用道具 举报

 楼主| 发表于 2020-4-12 18:23:02 | 显示全部楼层
smitest 发表于 2020-4-12 14:05
这个速度比起来意义不大啊。整个费时的比较才有效。

哦哦有道理,本身一开始这个任务和文件映射有关,所以就使用了文件映射这种方法,看来需要再增加循环次数或者改用其他的比较形式。
回复 赞! 靠!

使用道具 举报

发表于 2020-4-15 09:49:55 | 显示全部楼层
你说CPython映射文件速度的问题是在Windows下还是Linux下?屁眼不最终也是通过调用系统API来实现的吗?真的会比系统API还快?
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2020-4-17 12:02:09 | 显示全部楼层
系统消息 发表于 2020-4-15 09:49
你说CPython映射文件速度的问题是在Windows下还是Linux下?屁眼不最终也是通过调用系统API来实现的吗?真的 ...

我的那个cpython程序是在windows平台下的,后来我也反思了我的程序,感觉不是单一变量来试验了....
回复 赞! 靠!

使用道具 举报

发表于 2020-4-18 03:01:37 | 显示全部楼层
系统消息 发表于 2020-4-15 09:49
你说CPython映射文件速度的问题是在Windows下还是Linux下?屁眼不最终也是通过调用系统API来实现的吗?真的 ...

你需要注意的是,他每个循环里,都要打开一次文件,映射一下,再关闭文件。而且他在Windows下使用的是缓慢的A系API。这个测试没得什么意义,因为他比的不是通过映射来修改文件的速度,而是“映射的速度”。

就好比我GPU上传、下载纹理资源很慢,但进行图形处理的速度很快,可测速者每个循环都上传、下载纹理,然后只绘制一次,就得出结论“GPU没得CPU快”。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2020-4-18 22:17:42 | 显示全部楼层
0xAA55 发表于 2020-4-18 03:01
你需要注意的是,他每个循环里,都要打开一次文件,映射一下,再关闭文件。而且他在Windows下使用的是缓 ...

哦哦学到了,如果比文件映射还是要比读写速度;并且原来A系API要慢啊!
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-12-27 00:48 , Processed in 0.034899 second(s), 25 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表