慎用内存释放软件,剖析1Gram手机的内存原理

本文深入探讨了手机内存管理的原理,指出了一键清理内存的误区,强调了Android系统内建的内存回收机制,并分析了内存释放软件的无效性和资源浪费。建议用户合理利用内存资源,避免不必要的内存释放操作。
慎用内存释放软件,剖析1Gram手机的内存原理
2011年11月16日
  慎用内存释放软件,剖析1Gram手机的内存原理
  经常看见有人推荐一键释放内存的内存管理软件,或者说自己的ROM精简后可用内存有500+甚至600+……我个人是很不理解了……
  首先,从内存的原理说起。这个说起来比较长篇,举个简单的例子来说明:你去图书馆看书,书都在书架上,你会有一张桌子。对于电脑/手机来说,书架就是硬盘或者内置的ROM/SD卡,桌子就是内存,你就是CPU,书就是数据(程序本身也是数据,只是一种比较特殊的数据而已)。你看书的时候是从书架上把书取出来,放在桌子上看;CPU需要数据的时候,是从硬盘/SD卡把数据载入到内存之后,进行处理。
  那么大内存有什么好处呢?用图书馆的例子继续说明:如果你的桌子很小,刚看了几本书,你的桌子上已经摆满了,但现在你需要参考几本书架上的书,你只能把桌子上的若干本书放回书架(假设书籍不能堆叠着放),然后到书架去拿新书。但事实上,很有可能你放回书架的书呆会还需要继续看,你不得不在看完这几本书后,再把几本暂时不看的书放回书架,把刚才放回书架的书又拿出来……最后的结果是你一整天的时间,有大部分在不停的取书-放书-取书这个循环中浪费掉。而如果桌子足够大的话,就没有这个问题,你完全可以把整一个书架的书都放到桌子上,需要哪本就看哪本。
  对于1G ram的手机来讲,经常使用一键清理软件,把可用内存保持在500+,就相当于你本来有一张大桌子,但为了看着桌子干净整洁,所以当桌子上的书超过一定数量之后,你就把除了手上在看的那本书以外,全部都放回书架上……这个真的很没有必要,也很浪费资源。
  Android本身其实有很不错的内存回收机制,通常叫minfree:
  1、Android自身并没有所谓的关闭进程的说法 每当我们要退出一个进程回到桌面/打开另一个程序的时候我们只能按"返回键". 而当我们按下"返回键"后,该进程并没有真正的关闭,仍然保存在内存中. 这样在下次调用的时候可以更快的打开该程序
  2、要想真正的关闭一个已打开的进程,除了用第三方软件(例如advanced task manager)外,还有一个,那就是当Android系统认为当时已经没有足够的内存来运行新的进程,需要关闭一些虽然已经开着,但是没有用了(具体怎样一个进程才会被Android系统认为是"没有用"下面就要讨论到)的进程
  3、ActivityManagerService.java记录着每一个进程的优先级. 一个进程的oom_adj值也就代表了它的优先级. oom_adj值越高代表该进程优先级越低. 一个正在使用的进程的oom_adj值为0,一旦我们按下返回键,这个进程就会得到一个更高的oom_adj值(更低的优先级). 具体多少取决于该进程在LRU(last recently used) list的位置.(未证实)
  具体的细节保存在Android源文件drivers/misc/lowmemorykiller.c里
  4、Android将进程分为6个等级,它们按优先级顺序由高到低依次是:
  A、foreground app:前台程序,但事实上除了你正在运行的前台程序,还包括一些系统应用,例如启动器(或者叫桌面)、拨号程序、短信、联系人信息、USB服务等,对A4来说,还有Blur、温度监控、Moto门户、指纹等。
  B、visible app:“可见”程序,一般来说是输入法,对于A4来说,还有通话录音(国行)和进程管理。
  C、secondary service:服务程序,不过不包括系统服务。这些一般是第三方软件的后台服务了,在我的手机上,Go天气、七键开关、同花顺、PC助手之类的。
  D、hidden app:前台(不包括系统应用),如果按下“home”回到桌面或者通过长按“home”以及程序本身提供的切换、调用功能切换到其他程序的时候,这个程序就变成hidden app。
  E、content provider:直译就是内容提供者了。事实上这是没有客户端的secondary service,这个有点不好解释,举例来说明吧,A4有一个Blur相关的应用叫“切换小部件”,就是一个contend provider,如果你在桌面上通过添加“Moto小部件”增加Wifi/GPS/3G/蓝牙中的一个或者多个切换的桌面插件,它就有了客户端,变成了secondary service,如果你再把所有的切换插件从桌面删除,它就没有客户端了,又变回content provider。
  F、empty app:空应用。这个空进程有两种,第一种是你运行的程序,通过程序提供的退出操作退出程序,或者主界面按返回键退出到桌面的程序,并不会马上从内存释放掉这个程序的数据,android会把程序数据保留在内存里,这样你下次调用这个程序的时候,就不需要从ROM或者程序存储里面重新读取程序了。第二种,如果可用内存大于minfree里面的empty app设置,android会在空闲的时候,把没有运行过的程序数据读进内存。没错,就是你开机之后一直都没有运行过的程序!
  当然不会是所有安装了的程序都会预加载,从网上的资料观察来看,似乎在桌面上有快捷方式的,或者用的比较多的应用,Android都会预加载进内存。对于一般的释放内存软件来说,释放内存会把D、E、F三种程序结束掉,在A4上很搞笑的一件事情是,如果是未经修改(精简自带应用不算修改)的ROM,系统空闲的时候会自动读取存储,把结束掉的程序重新载入到内存(因为刚刚运行过,预载优先级会高一点),直到所有符合预载规则的程序都已经载入内存,或者可用内存低于minfree里面empty app(A4默认是)的设置值为止……也就是说:内存释放软件不但做的是无用功,而且增加了系统读取程序数据的操作。
  追求高可用内存的朋友,估计很大一部分是从256M/384M内存的手机过来,又或者习惯了Windows的内存管理方式的,但对于Android来说,其实只要可用内存大于你马上要运行的程序所需要的内存,不需要在运行程序的时候,系统要按照预设的规则结束掉一部分程序释放出足够的可用内存就OK了。也就是说,只要minfree里面empty app的设置值比你通常要运行的最占用内存的程序所需要的内存大一点就可以了。
  按照标准Android应用设计的程序,处于第四级、第五级的时候也不会消耗CPU资源的,而常见的内存释放软件也不会强制结束前三级的程序来释放内存,所以如果某个应用后台会耗电的话,只能说这个应用的开发者在设计的时候没有很好的理解Android应用的架构,或者出于其他原因,使用了service的方式运行,又或者在service没有client的时候还在进行一些操作。对于这样的程序,用任务管理软件单独杀掉应用程序的进程其实更好一点。
1. 问题描述 (2 points) 利用概率密度表进行概率密度计算,并利用Bayes公式对字符进行识别。 2. 问题的本质和分析 (10 points) 基于贝叶斯方法的手写字符识别是一种统计学习方法,它的本质是通过概率统计来模拟和推断手写字符的识别过程。这种方法利用贝叶斯定理,将观测到的手写字符数据与不同字符的概率模型相结合,以确定哪个字符最有可能与给定的手写输入匹配。 公式: 3. 解决问题的思路,方法,思路的可性行和预期结果表现(避免截图和word打入公式)(10 points) 数据收集和预处理:收集手写字符图像数据,清理和标准化图像。 特征提取:从图像中提取有用的特征,如像素值或轮廓信息。 建立模型:使用贝叶斯方法建立字符类别的概率模型,包括特征分布和先验概率。 训练模型:使用训练数据估计模型参数。 分类:对新的手写字符图像,计算每个字符类别的后验概率,选择概率最高的类别作为识别结果。 评估性能:使用测试数据集来评估模型的准确性。 改进和部署:根据性能评估结果改进模型,然后部署到实际应用中。 4. 解决问题中遇到的难点 (3 points) 数据质量不佳:手写字符数据可能受到噪音、模糊或变形的影响,导致识别难度增加。 特征提取:选择合适的特征和提取方法对于识别性能至关重要。不同字符可能需要不同的特征表示,而这通常需要领域知识。 数据不平衡:某些字符类别可能具有较少的样本,导致模型训练不均匀,影响性能。 参数调整:调整贝叶斯方法的参数,如平滑参数或深度学习模型的超参数,需要耗费时间和经验。 解释性:有时需要解释模型的决策过程,特别是在一些领域,如司法或医疗应用中。 5.与算法对应的关键代码的实现进行文字解释和注释, from sklearn import datasets import numpy as np import gzip def LoadMnistImages(filename): with gzip.open(filename, 'rb') as f: data = np.frombuffer(f.read(), np.uint8, offset=16) data = data.reshape(-1, 28 * 28) / 255.0 return data def LoadMnistLabels(filename): with gzip.open(filename, 'rb') as f: data = np.frombuffer(f.read(), np.uint8, offset=8) return data train_x = LoadMnistImages('train.gz') train_y = LoadMnistLabels('train_label.gz') test_x = LoadMnistImages('test.gz') test_y = LoadMnistLabels('test_label.gz') pz = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] pxz = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] grouped_image = [[], [], [], [], [], [], [], [], [], []] for y in range(10): num = 0 for idx, i in enumerate(train_y): if i == y: num = num + 1 grouped_image[y].append(train_x[idx]) pz[y] = num / len(test_y) accuracies = [] correct = 0 for idx, test in enumerate(test_x): for idx2, group in enumerate(grouped_image): for i in range(784): for g in group: if abs(g[i] - test[i]) < 5: # 阈值 pxz[idx2] = pxz[idx2] + 1 pxz[idx2] = pxz[idx2] / len(group) max = 0 for i in range(10): if pxz[i] + pz[i] > pxz[max] + pz[max]: max = i if max == test_y[idx]: correct = correct + 1 accuracy = correct / len(test_x) print('accuracy: ' + str(accuracy)) 5. 对结果进行分析,提出改进想法的动机,依据、算法设计和预期结果(10 points) 这个代码存在如下问题:比较图片的像素值时,认为只有像素值完全相同才时相同,这样会使准确率有所下降,如下: if g[i] == test[i]: pxz[idx2] = pxz[idx2] + 1 为了解决这个问题,我设定了阈值比较的方法: if abs(g[i] - test[i]) < 5: # 阈值 pxz[idx2] = pxz[idx2] + 1 通过for循环,找到一个最佳的阈值为5,使得准确率最高。 6.撰写格式 2.6.1 一致性的建议 整个项目的开发中,函数和对象的命名保持一致性,尤其是一个模块或者一个函数中的一致性更为重要。命名应遵循驼峰命名规则。 2.6.2 代码逻辑块 2.6.2.1 缩进 众所周知,python是通过缩进来进行代码布局的,使用pycharm可以配置几个空格来代表一个tab,从而来布局代码的缩进。 2.6.2.2 不混用Tab键和空格 Python 里有一句话叫“以用空格为荣,以用Tab键为耻”。但全用空格时确实很麻烦。因此,这里不限定用 Tab 键还是空格。但记住:不可混用! print("Hello world") #Tab换行 2.6.2.3 规范行的最大长度 如果代码的某一行很长,我们需要换行折叠观看代码,这会影响代码的阅读。因此,对顺序排放的大块代码,推荐长度在72个字节以内,另外推荐使用反斜杠续行。 if (first_result >=0.5 and second_result >=0.5) or \ #反斜杠续行 (second_result >=0.5 and last_result>=0.5) : last_result = max(first_result,second_result,last_result) 2.6.2.4 用代码块体现程序逻辑 通常,我们用两行分割顶层函数和类的定义, 用一行分割类成员方法的定义。 在一个函数内使用空格时请注意谨慎使用于一个逻辑段。 class A: a = 42 b = list(a + i for i in range(10)) 2.6.3 注释规范 注释是指对一部分代码的文字解释。注释内容必须跟代码保持一致,当你想修改代码时,建议优先修改注释。 注释必须是一个完整的句子。如果注释只有一句话,建议省略句末的句号。 注释块由一个或多个完整句子构成,每个句子应该以句号结尾。 注释阅读统一用英文或中文。使用统一注释格式有助于良好的习惯和团队的进步。 x = 1 # 给x赋值为1 2.6.3.1 注释块 注释块通常应用于跟随着一些(或者全部)代码并和这些代码有着相同的缩进层次。注释块中每行以’#'和一个空格开始(除非他是注释内的缩进文本)。 注释块内的段落以仅含单个’#'的行分割。 注释块上下方最好有一空行包围(或上方两行下方一行,对一个新函数定义段的注释)。 # print(a) # print(b) # # print(c) 2.6.3.2 行内注释 行内注释应该至少用两个空格和语句分开,他们应该以’#'和单个空格开始。 Dict = dict(zip(map(str,range(10)), range(10))) #创建长度为10的字典 图1-45行内注释 如果语意易理解,那么行内注释是不必要的,事实上是应该被去掉的,不要这样写: x = x + 1 #increment x 图1-46 注释错误示例 2.6.4 命名规范 命名规范在编写代码中起到很重要的作用,使用命名规范可以更加直观地了解代码所代表的含义,让代码具有有可读性、易写性与明义性。骆驼式命名法(Camel-Case)一词来自 Perl 语言中普遍使用的大小写混合格式,又称驼峰式命名法,是电脑程式编写时的一套命名规则(惯例),并无绝对与强制,为的是增加识别和可读性。驼峰式命名法是指混合使用大小写字母来构成变量和函数的名字。  驼峰命名法(CamelCase) 驼峰法(即帕斯卡命名法)单词首字母均大写。 class MyNaiveBayesClassifier:  匈牙利命名法(HN-case) 变量名可依次由属性、类型、描述组成。 iUserName #i为int类型缩写,UserName是描述。  蛇形命名法 函数通常全由小写字母和下划线组成,小写单词间用下划线连接 def use_naivebayes_as_classifier(): 2.6.4.1 命名风格 命名风格应该遵循驼峰命名规则。 class MyNaiveBayesClassifier: 此外,还需要注意: 1)单下划线作为前导,如:_single_begin,这是弱的内部使用标识,例如我们在M文件中定义如下变量: _a = 1 但是在使用"from M import *"的时候变量不会被导入; print(_a) # NameError: name '_a' is not defined 2)单下划线作为结尾的,如:single_end_,这一般用于避免跟 python 关键词冲突; class_ = "This is not a class, just a variable." 3)双下划线前导,如:__double_begin,类私有名; class Test: def __init__(self): self.__private_var = 10 4) 双下划线前导+结尾,如:double_begin_and_end,特殊对象或属性,存在于用户控制的命名空间中,如:int,__import__等。有时可以被用户定义,用于触发某个特殊行为,如运算符重载。 class Test: def __str__(self): return "This is a Test class." 2.6.4.2 应避免的对象命名 为了避免文字显示的差异,永远不要用: 1)小写字母“l”(小写的"L"); 2)大写字母“O”; 3)大写字母“I”(读音 eye); 作为单字符的变量名,因为不利于跟数字“0”和”1“很好的区分开来。 当要用小写字母“l”时,请用大写字母“L”代替。  变量名必须是一个有效的标识符;  变量名不用使用Python中的保留字;  慎用小写字面l和大写字母O;  应选用有意义的词作为变量; 尽量小写, 如有多个单词,用下划线隔开即采用蛇形命名法(snake_case)命名。 i = 0 if i > 0: number = 0 school_name = "Tsinhua" 常量采用全大写,如多个单词,用下划线隔开。 PAI = 3.14 MAX_CONNECTION = 100 CONNECTION_TIMEOUT = 500 2.6.4.3 模块命名 模块尽量使用小写命名,首字母保持小写,尽量不要用下划线(除非多个单词,且数量不多的情况)(加入下划线可改善可读性)。因为模块名被映射到文件名,有些文件系统大写不敏感并且截短长名字,模块名被选为相当短是重要的—这在 Unix 上不是问题,但当代码传到 Mac 或 Windows 上就可能是个问题了。 import decoder # 正确的模块名 import html_parser # 正确的模块名 import Decoder # 不推荐的模块名 2.6.4.4 类命名 类名总是使用首字母大写式驼峰,例如:ClassName(),命名单词串的约定。 Class ClassName(): #定义一个类 2.6.4.5 全局变量命名 这个的约定跟用于函数的约定差不多。那些模块,应该在那些不想被导入的全局变量(还有内部函数和类)前加一个下划线。例如:GLOBAL_VAR global GLOBAL_VAR #定义一个全局变量GLOBAL_VAR 2.6.4.6 函数命名 函数命名函数名应该为小写、动宾短语,可能用下划线风格单词以增加可读性。如:open_file()表示打开一个文件, def open_file(): #定义一个打开文件的函数 如果需要表明能快速打开一个文件,命名还可以为 def fast_open_file(): #定义一个快速打开文件的函数 class MyNaiveBayesClassifier: # 驼峰命名法定义一个类 '''定义一个NaiveBayesClassifier的类 #一句话总结这个类的功能和作,文档字符串需要用三引号包括 Attributes: class_probs: 类别先验概率(Numpy) feature_probs: 特征条件概率(Numpy) ''' def __init__(self): self.class_probs = None # 初始化参数 self.feature_probs = None def fit(self, X_train, y_train): '''定义fit函数计算朴素贝叶斯的训练过程 Attributes: X_train:训练使用的数据(Numpy) y_train:训练使用的类别(Numpy) ''' num_samples, num_features = X_train.shape #样本数量,特征数量 self.classes = np.unique(y_train) #类别数量 num_classes = len(self.classes) self.class_probs = np.zeros(num_classes) # 计算类别先验概率P(z) for i, c in enumerate(self.classes): self.class_probs[i] = np.sum(y_train == c) / num_samples # 计算特征条件概率P(x_1,x_2,…,x_784|z) self.feature_probs = np.zeros((num_classes, num_features)) for i, c in enumerate(self.classes): X_c = X_train[y_train == c] self.feature_probs[i] = (np.sum(X_c, axis=0) + 1) / #反斜杠实现换行 (np.sum(X_c) + num_features) def predict(self, X_test): '''定义predict函数计算类别先验概率P(z|X) Attributes: X_test:测试使用的数据(Numpy) return: 预测结果(Numpy) ''' num_samples, _ = X_test.shape num_classes, num_features = self.feature_probs.shape y_pred = np.zeros(num_samples) # 初始化预测结果数组 for i in range(num_samples): posteriors = np.zeros(num_classes) for j in range(num_classes): # 计算后验概率 posteriors[j] = np.log(self.class_probs[j]) + / np.sum(np.log(self.feature_probs[j]) * X_test[i]) y_pred[i] = np.argmax(posteriors) return y_pred 按上述格式撰写报告,且满足撰写格式
10-21
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值