技术宅的结界

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

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 305|回复: 6
收起左侧

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

[复制链接]

24

主题

256

帖子

1518

积分

用户组: 上·技术宅

UID
3808
精华
6
威望
53 点
宅币
1036 个
贡献
90 次
宅之契约
0 份
在线时间
253 小时
注册时间
2018-5-6
发表于 2020-4-2 18:16:04 | 显示全部楼层 |阅读模式

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

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

x
本帖最后由 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代码:
[Python] 纯文本查看 复制代码
# coding:utf-8

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

import time
import mmap
import os
import os.path

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


def MapFile(filename):
    with open(filename, 'rb') as f:
        mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
        bytes_content = mm.read()
        mm.close()
    
    # Decode the bytes string.
    try:
        content = bytes_content.decode('utf-8')
    except:
        content = bytes_content.decode('GB2312')
    
    # Output the file content.
    #print(content)

def main():
    
    # Start time.
    t1 = time.time()
    
    # Begin the 100 cycles.
    for i in range(100):
        # Check the file size.
        if CheckFileEmpty('temp.txt') == 0:
            print('The file size if zero, please choose another file.')
            return
        
        # Begin to file mapping.
        MapFile('temp.txt')
    
    t2 = time.time()
    
    print('Time consuming: ', str(t2-t1)[:9], 's')

if __name__ == '__main__':
    main()


Linux上C语言:
[C] 纯文本查看 复制代码
// This program's time cosumption is 0.005s

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <sys/time.h>

#ifndef BUF_SIZE
#define BUF_SIZE 4096
#endif


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

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

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


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

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

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

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

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

label1:
	free(ptr);
label2:
	close(fp);
	//printf("[ * ] All done!\n");
	return ;
}




int main(int argc, char *argv[])
{
	struct timeval starttime;
	struct timeval endtime;
	double timeuse = 0;
	
	gettimeofday(&starttime, NULL);
	// 100 cycles;
	for(int i = 0;i<100;i++)
		FileMap("hello.txt");
	gettimeofday(&endtime, NULL);

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

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

	return 0;
}

	


利用Windows API 编写的文件映射读取的C语言程序:
[C] 纯文本查看 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

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

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

	BOOL status = GetFileAttributesExA(filename,
		GetFileExInfoStandard,
		(LPVOID)&ws32);
	
	if (status == FALSE)
	{
		printf("GetFileAttributesExA has a error, the code is: %d\n", GetLastError());
		return 0;
	}

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

}


// Use the file mapping to get the content of target file.
VOID FileMapping(CHAR *filename)
{
	// Get the file open handle.
	HANDLE hFile = CreateFileA(filename,
		GENERIC_READ | GENERIC_WRITE,
		0,	// Exclusive this process to manipulate the file.
		NULL,
		OPEN_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		printf("CreateFileA has error, the code is: %d\n", GetLastError());
		return;
	}

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

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

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

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


label1:
	CloseHandle(hFileMap);
	//printf("Close handle: hFileMap.\n");
label2:
	CloseHandle(hFile);
	//printf("Close handle: hFile.\n");
	UnmapViewOfFile((LPCVOID)lpFileMap);
	//printf("Unmap the file: lpFileMap.\n");
	return;
}


int main(int argc, char *argv[])
{
	// Begin to count the program running time.
	LARGE_INTEGER foreTime = { 0 };
	LARGE_INTEGER backTime = { 0 };
	LARGE_INTEGER freq = { 0 };
	
	// Used to set the specific threads have connection to 
	// which CPU cores.
	QueryPerformanceFrequency(&freq);
	QueryPerformanceCounter(&foreTime);

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

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

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

	QueryPerformanceCounter(&backTime);

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

	return 0;
}


实验文本:temp.txt/test.txt/hello.txt
[AppleScript] 纯文本查看 复制代码
Hello world!


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

		.386
		.model	flat, stdcall
		option	casemap:none

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; include 文件
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

include		windows.inc
include		kernel32.inc
includelib	kernel32.lib
include		user32.inc
includelib	user32.lib


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 数据段
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

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



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 代码段
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

		.code

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

_CheckEmptyFile endp

; 文件映射
; 无返回值
_FileMapping	proc	uses ecx
		LOCAL	@hFile:dword
		LOCAL	@hFileMap:dword
		LOCAL	@errorCode:dword
		; 存储文件映射读取出来的内容的首地址
		LOCAL 	@content_addr:dword
		LOCAL	@mapViewofFile
		
		; 打开文件
		invoke	CreateFile, offset szFileName, GENERIC_READ or GENERIC_WRITE, \
			0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL

		mov	@hFile, eax
		
		
		.if	eax == INVALID_HANDLE_VALUE
		@@:
			; 获取last error
			call	GetLastError
			invoke	wsprintf, addr @errorCode, offset szErrorCode, eax
			invoke	MessageBox, NULL, addr @errorCode, offset szError, MB_OK
			xor	eax, eax
			ret
		.endif
		
		; 创建一个文件映射对象
		invoke	CreateFileMapping, @hFile, NULL, \
			PAGE_READWRITE, 0, 0, offset szFileName
		
		mov	@hFileMap, eax
		
		.if	eax == INVALID_HANDLE_VALUE
			invoke	CloseHandle, @hFile
			jmp	@B
		.endif
		
		; 获取文件内容
		invoke	MapViewOfFile, @hFileMap, \
			FILE_MAP_READ, 0, 0, 0
		
		.if	eax == NULL
			invoke	CloseHandle, @hFile
			invoke	CloseHandle, @hFileMap
			jmp	@B
		.endif
		
		mov	@content_addr, eax
		; 显示文件映射的内容
		;invoke	MessageBox, NULL, @content_addr, offset szMsgCaption, MB_OK
		
		; 关闭句柄
		invoke	CloseHandle, @hFile
		invoke	CloseHandle, @hFileMap
		invoke	UnmapViewOfFile, @mapViewofFile
	
	xor	eax, eax
	ret
_FileMapping endp


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

			; 循环100次文件映射的过程
			mov	ecx, 100
			@@:
				call	_CheckEmptyFile
				.if	eax == 0
					invoke	MessageBox, NULL, offset szEmptyFile, offset szMsgCaption, MB_OK
					ret
				.else
					call	_FileMapping
				.endif
			loop	@B

			; 结束计时
			invoke	QueryPerformanceCounter, offset dqBackTime
			
			; 处理浮点数,参考罗云彬《Windows环境下32位汇编语言程序设计》
			mov	eax, dword ptr dqForeTime
			mov	edx, dword ptr dqForeTime + 4
			sub	dword ptr dqBackTime, eax
			sbb	dword ptr dqBackTime + 4, edx
			
			finit
			fild	dqFreq
			fild	dqBackTime
			fimul	dwlm
			fdivr
			fistp	dqTime ; μs
			
			invoke	wsprintf, addr @time_addr, offset szFormat, dqTime
			invoke	MessageBox, NULL, addr @time_addr, offset szMsgCaption, MB_OK
		
		
		xor	eax, eax
		ret
_TimeConsumption endp



; 主程序
start:
		call	_TimeConsumption
		invoke	ExitProcess, NULL

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
菜鸟一枚,直接指正,不必留情
回复

使用道具 举报

1

主题

17

帖子

117

积分

用户组: 小·技术宅

UID
5585
精华
0
威望
6 点
宅币
88 个
贡献
0 次
宅之契约
0 份
在线时间
11 小时
注册时间
2020-2-5
发表于 2020-4-12 14:05:07 | 显示全部楼层
这个速度比起来意义不大啊。整个费时的比较才有效。

24

主题

256

帖子

1518

积分

用户组: 上·技术宅

UID
3808
精华
6
威望
53 点
宅币
1036 个
贡献
90 次
宅之契约
0 份
在线时间
253 小时
注册时间
2018-5-6
 楼主| 发表于 2020-4-12 18:23:02 | 显示全部楼层
smitest 发表于 2020-4-12 14:05
这个速度比起来意义不大啊。整个费时的比较才有效。

哦哦有道理,本身一开始这个任务和文件映射有关,所以就使用了文件映射这种方法,看来需要再增加循环次数或者改用其他的比较形式。
菜鸟一枚,直接指正,不必留情

3

主题

53

帖子

839

积分

用户组: 大·技术宅

UID
4293
精华
3
威望
11 点
宅币
585 个
贡献
164 次
宅之契约
0 份
在线时间
61 小时
注册时间
2018-9-19
发表于 2020-4-15 09:49:55 | 显示全部楼层
你说CPython映射文件速度的问题是在Windows下还是Linux下?屁眼不最终也是通过调用系统API来实现的吗?真的会比系统API还快?

24

主题

256

帖子

1518

积分

用户组: 上·技术宅

UID
3808
精华
6
威望
53 点
宅币
1036 个
贡献
90 次
宅之契约
0 份
在线时间
253 小时
注册时间
2018-5-6
 楼主| 发表于 2020-4-17 12:02:09 | 显示全部楼层
系统消息 发表于 2020-4-15 09:49
你说CPython映射文件速度的问题是在Windows下还是Linux下?屁眼不最终也是通过调用系统API来实现的吗?真的 ...

我的那个cpython程序是在windows平台下的,后来我也反思了我的程序,感觉不是单一变量来试验了....
菜鸟一枚,直接指正,不必留情

1055

主题

2415

帖子

6万

积分

用户组: 管理员

一只技术宅

UID
1
精华
221
威望
328 点
宅币
19262 个
贡献
39852 次
宅之契约
0 份
在线时间
1821 小时
注册时间
2014-1-26
发表于 2020-4-18 03:01:37 | 显示全部楼层
系统消息 发表于 2020-4-15 09:49
你说CPython映射文件速度的问题是在Windows下还是Linux下?屁眼不最终也是通过调用系统API来实现的吗?真的 ...

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

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

24

主题

256

帖子

1518

积分

用户组: 上·技术宅

UID
3808
精华
6
威望
53 点
宅币
1036 个
贡献
90 次
宅之契约
0 份
在线时间
253 小时
注册时间
2018-5-6
 楼主| 发表于 2020-4-18 22:17:42 | 显示全部楼层
0xAA55 发表于 2020-4-18 03:01
你需要注意的是,他每个循环里,都要打开一次文件,映射一下,再关闭文件。而且他在Windows下使用的是缓 ...

哦哦学到了,如果比文件映射还是要比读写速度;并且原来A系API要慢啊!
菜鸟一枚,直接指正,不必留情

本版积分规则

QQ|申请友链||Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图  

GMT+8, 2020-6-4 17:52 , Processed in 0.109376 second(s), 33 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

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