当前位置: 首页 > 编程日记 > 正文

深入探讨PHP中的内存管理问题

一、 内存

PHP中,填充一个字符串变量相当简单,这只需要一个语句"?php $str = 'hello world '; ?"即可,并且该字符串能够被自由地修改、拷贝和移动。而在C语言中,尽管你能够编写例如"char *str = "hello world ";"这样的一个简单的静态字符串;但是,却不能修改该字符串,因为它生存于程序空间内。为了创建一个可操纵的字符串,你必须分配一个内存块,并且通过一个函数(例如strdup())来复制其内容。

{
char *str;
str = strdup("hello world");
if (!str) {
fprintf(stderr, "Unable to allocate memory!");
}
}


由于后面我们将分析的各种原因,传统型内存管理函数(例如malloc()free()strdup()realloc()calloc(),等等)几乎都不能直接为PHP源代码所使用。

二、 释放内存

在几乎所有的平台上,内存管理都是通过一种请求和释放模式实现的。首先,一个应用程序请求它下面的层(通常指"操作系统")"我想使用一些内存空间"。如果存在可用的空间,操作系统就会把它提供给该程序并且打上一个标记以便不会再把这部分内存分配给其它程序。
当应用程序使用完这部分内存,它应该被返回到OS;这样以来,它就能够被继续分配给其它程序。如果该程序不返回这部分内存,那么OS无法知道是否这块内存不再使用并进而再分配给另一个进程。如果一个内存块没有释放,并且所有者应用程序丢失了它,那么,我们就说此应用程序"存在漏洞",因为这部分内存无法再为其它程序可用。

在一个典型的客户端应用程序中,较小的不太经常的内存泄漏有时能够为OS"容忍",因为在这个进程稍后结束时该泄漏内存会被隐式返回到OS。这并没有什么,因为OS知道它把该内存分配给了哪个程序,并且它能够确信当该程序终止时不再需要该内存。

而对于长时间运行的服务器守护程序,包括象Apache这样的web服务器和扩展php模块来说,进程往往被设计为相当长时间一直运行。因为OS不能清理内存使用,所以,任何程序的泄漏-无论是多么小-都将导致重复操作并最终耗尽所有的系统资源。

现在,我们不妨考虑用户空间内的stristr()函数;为了使用大小写不敏感的搜索来查找一个字符串,它实际上创建了两个串的各自的一个小型副本,然后执行一个更传统型的大小写敏感的搜索来查找相对的偏移量。然而,在定位该字符串的偏移量之后,它不再使用这些小写版本的字符串。如果它不释放这些副本,那么,每一个使用stristr()的脚本在每次调用它时都将泄漏一些内存。最后,web服务器进程将拥有所有的系统内存,但却不能够使用它。

你可以理直气壮地说,理想的解决方案就是编写良好、干净的、一致的代码。这当然不错;但是,在一个象PHP解释器这样的环境中,这种观点仅对了一半。

三、 错误处理

为了实现"跳出"对用户空间脚本及其依赖的扩展函数的一个活动请求,需要使用一种方法来完全"跳出"一个活动请求。这是在Zend引擎内实现的:在一个请求的开始设置一个"跳出"地址,然后在任何die()exit()调用或在遇到任何关键错误(E_ERROR)时执行一个longjmp()以跳转到该"跳出"地址。

尽管这个"跳出"进程能够简化程序执行的流程,但是,在绝大多数情况下,这会意味着将会跳过资源清除代码部分(例如free()调用)并最终导致出现内存漏洞。现在,让我们来考虑下面这个简化版本的处理函数调用的引擎代码:

void call_function(const char *fname, int fname_len TSRMLS_DC){
zend_function *fe;
char *lcase_fname;
/* PHP函数名是大小写不敏感的,
*为了简化在函数表中对它们的定位,
*所有函数名都隐含地翻译为小写的
*/
lcase_fname = estrndup(fname, fname_len);
zend_str_tolower(lcase_fname, fname_len);
if (zend_hash_find(EG(function_table)lcase_fname, fname_len + 1, (void **)&fe) == FAILURE) {
zend_execute(fe-op_array TSRMLS_CC);
} else {
php_error_docref(NULL TSRMLS_CC, E_ERROR"Call to undefined function: %s()", fname);
}
efree(lcase_fname);
}


当执行到php_error_docref()这一行时,内部错误处理器就会明白该错误级别是critical,并相应地调用longjmp()来中断当前程序流程并离开call_function()函数,甚至根本不会执行到efree(lcase_fname)这一行。你可能想把efree()代码行移动到zend_error()代码行的上面;但是,调用这个call_function()例程的代码行会怎么样呢?fname本身很可能就是一个分配的字符串,并且,在它被错误消息处理使用完之前,你根本不能释放它。

注意,这个php_error_docref()函数是trigger_error()函数的一个内部等价实现。它的第一个参数是一个将被添加到docref的可选的文档引用。第三个参数可以是任何我们熟悉的E_*家族常量,用于指示错误的严重程度。第四个参数(最后一个)遵循printf()风格的格式化和变量参数列表式样。

四、 Zend内存管理器

在上面的"跳出"请求期间解决内存泄漏的方案之一是:使用Zend内存管理(ZendMM)层。引擎的这一部分非常类似于操作系统的内存管理行为-分配内存给调用程序。区别在于,它处于进程空间中非常低的位置而且是"请求感知"的;这样以来,当一个请求结束时,它能够执行与OS在一个进程终止时相同的行为。也就是说,它会隐式地释放所有的为该请求所占用的内存。图1展示了ZendMMOS以及PHP进程之间的关系。


1.Zend内存管理器代替系统调用来实现针对每一种请求的内存分配。


除了提供隐式内存清除功能之外,ZendMM还能够根据php.inimemory_limit的设置控制每一种内存请求的用法。如果一个脚本试图请求比系统中可用内存更多的内存,或大于它每次应该请求的最大量,那么,ZendMM将自动地发出一个E_ERROR消息并且启动相应的"跳出"进程。这种方法的一个额外优点在于,大多数内存分配调用的返回值并不需要检查,因为如果失败的话将会导致立即跳转到引擎的退出部分。

PHP内部代码和OS的实际的内存管理层""在一起的原理并不复杂:所有内部分配的内存都要使用一组特定的可选函数实现。例如,PHP代码不是使用malloc(16)来分配一个16字节内存块而是使用了emalloc(16)。除了实现实际的内存分配任务外,ZendMM还会使用相应的绑定请求类型来标志该内存块;这样以来,当一个请求"跳出"时,ZendMM可以隐式地释放它。

经常情况下,内存一般都需要被分配比单个请求持续时间更长的一段时间。这种类型的分配(因其在一次请求结束之后仍然存在而被称为"永久性分配"),可以使用传统型内存分配器来实现,因为这些分配并不会添加ZendMM使用的那些额外的相应于每种请求的信息。然而有时,直到运行时刻才会确定是否一个特定的分配需要永久性分配,因此ZendMM导出了一组帮助宏,其行为类似于其它的内存分配函数,但是使用最后一个额外参数来指示是否为永久性分配。

如果你确实想实现一个永久性分配,那么这个参数应该被设置为1;在这种情况下,请求是通过传统型malloc()分配器家族进行传递的。然而,如果运行时刻逻辑认为这个块不需要永久性分配;那么,这个参数可以被设置为零,并且调用将会被调整到针对每种请求的内存分配器函数。

例如,pemalloc(buffer_len1)将映射到malloc(buffer_len),而pemalloc(buffer_len0)将被使用下列语句映射到emalloc(buffer_len)

#define in Zend/zend_alloc.h:
#define pemalloc(size, persistent) ((persistent)?malloc(size): emalloc(size))


所有这些在ZendMM中提供的分配器函数都能够从下表中找到其更传统的对应实现。

表格1展示了ZendMM支持下的每一个分配器函数以及它们的e/pe对应实现:

表格1.传统型相对于PHP特定的分配器。

分配器函数

e/pe对应实现

void *malloc(size_t count);

void *emalloc(size_t count);void *pemalloc(size_t countchar persistent);

void *calloc(size_t count);

void *ecalloc(size_t count);void *pecalloc(size_t countchar persistent);

void *realloc(void *ptrsize_t count);

void *erealloc(void *ptrsize_t count);
void *perealloc(void *ptrsize_t countchar persistent);

void *strdup(void *ptr);

void *estrdup(void *ptr);void *pestrdup(void *ptrchar persistent);

void free(void *ptr);

void efree(void *ptr);
void pefree(void *ptrchar persistent);


你可能会注意到,即使是pefree()函数也要求使用永久性标志。这是因为在调用pefree()时,它实际上并不知道是否ptr是一种永久性分配。针对一个非永久性分配调用free()能够导致双倍的空间释放,而针对一种永久性分配调用efree()有可能会导致一个段错误,因为内存管理器会试图查找并不存在的管理信息。因此,你的代码需要记住它分配的数据结构是否是永久性的。 

除了分配器函数核心部分外,还存在其它一些非常方便的ZendMM特定的函数,例如:

void *estrndup(void *ptrint len);


该函数能够分配len+1个字节的内存并且从ptr处复制len个字节到最新分配的块。这个estrndup()函数的行为可以大致描述如下:

void *estrndup(void *ptr, int len)
{
char *dst = emalloc(len + 1);
memcpy(dst, ptr, len);
dst[len] = 0;
return dst;
}


在此,被隐式放置在缓冲区最后的NULL字节可以确保任何使用estrndup()实现字符串复制操作的函数都不需要担心会把结果缓冲区传递给一个例如printf()这样的希望以为NULL为结束符的函数。当使用estrndup()来复制非字符串数据时,最后一个字节实质上都浪费了,但其中的利明显大于弊。

void *safe_emalloc(size_t size, size_t count, size_t addtl);
void *safe_pemalloc(size_t size, size_t countsize_t addtlchar persistent);


这些函数分配的内存空间最终大小是((size*count)+addtl)。你可以会问:"为什么还要提供额外函数呢?为什么不使用一个emalloc/pemalloc呢?"原因很简单:为了安全。尽管有时候可能性相当小,但是,正是这一"可能性相当小"的结果导致宿主平台的内存溢出。这可能会导致分配负数个数的字节空间,或更有甚者,会导致分配一个小于调用程序要求大小的字节空间。而safe_emalloc()能够避免这种类型的陷井-通过检查整数溢出并且在发生这样的溢出时显式地预以结束。

注意,并不是所有的内存分配例程都有一个相应的p*对等实现。例如,不存在pestrndup(),并且在PHP 5.1版本前也不存在safe_pemalloc()

五、 引用计数

慎重的内存分配与释放对于PHP(它是一种多请求进程)的长期性能有极其重大的影响;但是,这还仅是问题的一半。为了使一个每秒处理上千次点击的服务器高效地运行,每一次请求都需要使用尽可能少的内存并且要尽可能减少不必要的数据复制操作。请考虑下列PHP代码片断:

?php
$a = 'Hello World';
$b = $a;
unset($a);
?


在第一次调用之后,只有一个变量被创建,并且一个12字节的内存块指派给它以便存储字符串"Hello World",还包括一个结尾处的NULL字符。现在,让我们来观察后面的两行:$b被置为与变量$a相同的值,然后变量$a被释放。

如果PHP因每次变量赋值都要复制变量内容的话,那么,对于上例中要复制的字符串还需要复制额外的12个字节,并且在数据复制期间还要进行另外的处理器加载。这一行为乍看起来有点荒谬,因为当第三行代码出现时,原始变量被释放,从而使得整个数据复制显得完全不必要。其实,我们不妨再远一层考虑,让我们设想当一个10MB大小的文件的内容被装载到两个变量中时会发生什么。这将会占用20MB的空间,此时,10已经足够了。引擎会把那么多的时间和内存浪费在这样一种无用的努力上吗?

你应该知道,PHP的设计者早已深谙此理。

记住,在引擎中,变量名和它们的值实际上是两个不同的概念。值本身是一个无名的zval*存储体(在本例中,是一个字符串值),它被通过zend_hash_add()赋给变量$a。如果两个变量名都指向同一个值,会发生什么呢?

{
zval *helloval;
MAKE_STD_ZVAL(helloval);
ZVAL_STRING(helloval, "Hello World", 1);
zend_hash_add(EG(active_symbol_table), "a", sizeof("a")&helloval, sizeof(zval*), NULL);
zend_hash_add(EG(active_symbol_table), "b", sizeof("b")&helloval, sizeof(zval*), NULL);
}


此时,你可以实际地观察$a$b,并且会看到它们都包含字符串"Hello World"。遗憾的是,接下来,你继续执行第三行代码"unset($a);"。此时,unset()并不知道$a变量指向的数据还被另一个变量所使用,因此它只是盲目地释放掉该内存。任何随后的对变量$b的存取都将被分析为已经释放的内存空间并因此导致引擎崩溃。

这个问题可以借助于zval(它有好几种形式)的第四个成员refcount加以解决。当一个变量被首次创建并赋值时,它的refcount被初始化为1,因为它被假定仅由最初创建它时相应的变量所使用。当你的代码片断开始把helloval赋给$b时,它需要把refcount的值增加为2;这样以来,现在该值被两个变量所引用: 

{
zval *helloval;
MAKE_STD_ZVAL(helloval);
ZVAL_STRING(helloval, "Hello World", 1);
zend_hash_add(EG(active_symbol_table), "a", sizeof("a")&helloval, sizeof(zval*), NULL);
ZVAL_ADDREF(helloval);
zend_hash_add(EG(active_symbol_table), "b", sizeof("b")&hellovalsizeof(zval*)NULL);
}


现在,当unset()删除原变量的$a相应的副本时,它就能够从refcount参数中看到,还有另外其他人对该数据感兴趣;因此,它应该只是减少refcount的计数值,然后不再管它。

六、 写复制(Copy on Write

通过refcounting来节约内存的确是不错的主意,但是,当你仅想改变其中一个变量的值时情况会如何呢?为此,请考虑下面的代码片断:

?php
$a = 1;
$b = $a;
$b += 5;
?


通过上面的逻辑流程,你当然知道$a的值仍然等于1,而$b的值最后将是6。并且此时,你还知道,Zend在尽力节省内存-通过使$a$b都引用相同的zval(见第二行代码)。那么,当执行到第三行并且必须改变$b变量的值时,会发生什么情况呢?

回答是,Zend要查看refcount的值,并且确保在它的值大于1时对之进行分离。在Zend引擎中,分离是破坏一个引用对的过程,正好与你刚才看到的过程相反:

zval *get_var_and_separate(char *varname, int varname_len TSRMLS_DC)
{
zval **varval, *varcopy;
if (zend_hash_find(EG(active_symbol_table)varname, varname_len + 1, (void**)&varval) == FAILURE) {
/* 变量根本并不存在-失败而导致退出*/
return NULL;
}
if ((*varval)-refcount < 2) {
/* varname是唯一的实际引用,
*不需要进行分离
*/
return *varval;
}
/* 否则,再复制一份zval*的值*/
MAKE_STD_ZVAL(varcopy);
varcopy = *varval;
/* 复制任何在zval*内的已分配的结构*/
zval_copy_ctor(varcopy);
/*删除旧版本的varname
*这将减少该过程中varvalrefcount的值
*/
zend_hash_del(EG(active_symbol_table), varname, varname_len + 1);
/*初始化新创建的值的引用计数,并把它依附到
* varname变量
*/
varcopy-refcount = 1;
varcopy-is_ref = 0;
zend_hash_add(EG(active_symbol_table), varname, varname_len + 1&varcopy, sizeof(zval*), NULL);
/*返回新的zval* */
return varcopy;
}


现在,既然引擎有一个仅为变量$b所拥有的zval*(引擎能知道这一点),所以它能够把这个值转换成一个long型值并根据脚本的请求给它增加5

七、 写改变(change-on-write

引用计数概念的引入还导致了一个新的数据操作可能性,其形式从用户空间脚本管理器看来与"引用"有一定关系。请考虑下列的用户空间代码片断:

?php
$a = 1;
$b = &$a;
$b += 5;
?


在上面的PHP代码中,你能看出$a的值现在为6,尽管它一开始为1并且从未(直接)发生变化。之所以会发生这种情况是因为当引擎开始把$b的值增加5时,它注意到$b是一个对$a的引用并且认为"我可以改变该值而不必分离它,因为我想使所有的引用变量都能看到这一改变"

但是,引擎是如何知道的呢?很简单,它只要查看一下zval结构的第四个和最后一个元素(is_ref)即可。这是一个简单的开/关位,它定义了该值是否实际上是一个用户空间风格引用集的一部分。在前面的代码片断中,当执行第一行时,为$a创建的值得到一个refcount1,还有一个is_ref值为0,因为它仅为一个变量($a)所拥有并且没有其它变量对它产生写引用改变。在第二行,这个值的refcount元素被增加为2,除了这次is_ref元素被置为1之外(因为脚本中包含了一个"&"符号以指示是完全引用)。

最后,在第三行,引擎再一次取出与变量$b相关的值并且检查是否有必要进行分离。这一次该值没有被分离,因为前面没有包括一个检查。下面是get_var_and_separate()函数中与refcount检查有关的部分代码:

if ((*varval)-is_ref || (*varval)-refcount < 2) {
/* varname是唯一的实际引用,
或者它是对其它变量的一个完全引用
*任何一种方式:都没有进行分离
*/
return *varval;
}


这一次,尽管refcount2,却没有实现分离,因为这个值是一个完全引用。引擎能够自由地修改它而不必关心其它变量值的变化。

八、 分离问题

尽管已经存在上面讨论到的复制和引用技术,但是还存在一些不能通过is_refrefcount操作来解决的问题。请考虑下面这个PHP代码块:

?php
$a = 1;
$b = $a;
$c = &$a;
?


在此,你有一个需要与三个不同的变量相关联的值。其中,两个变量是使用了"change-on-write"完全引用方式,而第三个变量处于一种可分离的"copy-on-write"(写复制)上下文中。如果仅使用is_refrefcount来描述这种关系,有哪些值能够工作呢?

回答是:没有一个能工作。在这种情况下,这个值必须被复制到两个分离的zval*中,尽管两者都包含完全相同的数据(见图2)


2.引用时强制分离


同样,下列代码块将引起相同的冲突并且强迫该值分离出一个副本(见图3)


3.复制时强制分离

?php
$a = 1;
$b = &$a;
$c = $a;
?


注意,在这里的两种情况下,$b都与原始的zval对象相关联,因为在分离发生时引擎无法知道介于到该操作当中的第三个变量的名字。

九、 总结

PHP是一种托管语言。从普通用户角度来看,这种仔细地控制资源和内存的方式意味着更为容易地进行原型开发并导致出现更少的冲突。然而,当我们深入"内里"之后,一切的承诺似乎都不复存在,最终还要依赖于真正有责任心的开发者来维持整个运行时刻环境的一致性。

相关文章:

介绍一个效率爆表的数据采集框架

作者 | 俊欣来源丨关于数据分析与可视化今天我们来聊一下如何用协程来进行数据的抓取,协程又称为是微线程,也被称为是用户级线程,在单线程的情况下完成多任务,多个任务按照一定顺序交替执行。那么aiohttp模块在Python中作为异步的…

最多显示6行并且最多显示三条文本

为什么80%的码农都做不了架构师&#xff1f;>>> private void setCommentContent(ViewHolder vh, String feedId, int commentNum, ArrayList<CommentItem> comment_lists){if(commentNum < 0 || comment_lists null || comment_lists.isEmpty()){for(in…

【刷算法】LeetCode- 两数之和

题目描述 给定一个整数数组和一个目标值&#xff0c;找出数组中和为目标值的两个数。 你可以假设每个输入只对应一种答案&#xff0c;且同样的元素不能被重复利用。 示例: 给定 nums [2, 7, 11, 15], target 9因为 nums[0] nums[1] 2 7 9 所以返回 [0, 1] 复制代码分析 第…

栈区和堆区内存分配区别

一直以来总是对这个问题的认识比较朦胧&#xff0c;我相信很多朋友也是这样的&#xff0c;总是听到内存一会在栈上分配&#xff0c;一会又在堆上分配&#xff0c;那么它们之间到底是怎么的区别呢&#xff1f;为了说明这个问题&#xff0c;我们先来看一下内存内部的组织情况&…

高精度进制转换

高精度进制转换&#xff1a; 对于普通的不是非常大的数的相互转换&#xff0c;我们一般採用不断模取余的方法&#xff0c;比如&#xff1a;将10进制数m转换成n进制数&#xff0c;则对m模n取余就可以。可是&#xff0c;假设是一个有几百、几千或者很多其它位的大数呢&#xff1f…

远程办公,你希望在家工作几天?

受疫情影响&#xff0c;员工的工作方式不得不发生改变。在过去短短的几个月内&#xff0c;远程办公从偶然一次变成了常态化。随着疫情的反复&#xff0c;远程办公再次成为了许多企业的选择。3月份携程正式启动了“32”混合办公模式&#xff0c;即每周有1-2天&#xff0c;员工可…

python爬虫日志(9)爬取代理

2019独角兽企业重金招聘Python工程师标准>>> 话不多说&#xff0c;直接上代码&#xff0c;很简单&#xff0c;很容易看懂 import requests from bs4 import BeautifulSoup import randomdef get_ip_list():print("正在获取代理列表...")ip_url http://ww…

使php支持mbstring库以及使用

1.执行yum install php-mbstring2. 修改php.ini (这一步非常重要, 部分lxadmin版本无法自动修改)echo ‘extensionmbstring.so’ >>/etc/php.ini #更具php安装目录而定3. 重启web service如果是apache: service httpd restart方法二&#xff1a;php 5.36安装目录&#xf…

仿余额宝数字跳动效果 TextCounter

1、TextCounter 效果 2、TextCounter 说明 每次打开余额宝第一件事情就去看看有多少钱&#xff0c;最炫的就是看着钱在跳动相当的舒服&#xff0c;今天放出这个效果。 温馨提示&#xff1a;支持的Android版本最低的是Android 4.0.0 IceCreamSandwich &#xff08; API等级14 &a…

年仅 16 岁的黑客少年,竟是搅乱 IT 巨头的幕后主使?

整理 | 郑丽媛出品 | CSDN近来&#xff0c;黑客组织 Lapsus$ 活跃在各大科技网站&#xff1a;窃取英伟达近 1TB 的数据、泄露三星近 190GB 的机密数据、公布微软 Bing 和 Cortana 源码…不同于大部分黑客组织&#xff0c;Lapsus$ 没有刻意隐藏自己&#xff0c;反而行事非常高调…

使用硬盘,安装双系统,Win7+CentOS

我用那个U盘装了很多次都不行&#xff0c;都是说找不到文件。最后就找了一篇博客看如何安装双系统&#xff0c;最后发现原来可以用硬盘安装的。经过5个多小时终于完成了。^-^。 1.首先是分区&#xff0c;可以使用Window7自带的磁盘管理程序进行分区。&#xff08;PS 我是用Cent…

Linux 文件系统剖析

Linux 文件系统剖析 按照分层结构讨论 Linux 文件系统 M. Tim Jones, 顾问工程师, Emulex Corp. 简介&#xff1a; 在文件系统方面&#xff0c;Linux 可以算得上操作系统中的 “瑞士军刀”。Linux 支持许多种文件系统&#xff0c;从日志型 文件系统到集群文件系统和加密文件系统…

Docker构建Nginx+Tomcat动静分离架构

随着主流Nginx WEB服务器的发展&#xff0c;现在基于Nginx的WEB服务器已广泛应用于各大互联网企业。今天我们来使用docker构建我们的LinuxNginxTomcat动静分离服务器。1) 启动docker镜像查看当前系统存在的镜像&#xff0c;我这里为CentOS6.6&#xff0c;大家可以参考我第一…

硬核!Python 四种变量的代码对象和反汇编分析

作者 | 大奎整理 | 阳哥来源丨Python数据之道在Python基础的学习过程中&#xff0c;对变量和参数的理解有助于我们从更基础层面了解Python语言的运行。在这个过程中&#xff0c;还是有不少冷门和细节的地方需要进一步熟悉。今天我们来分享Python四种变量的代码对象和反汇编分析…

Python--数据存储:pickle模块的使用讲解

在机器学习中&#xff0c;我们常常需要把训练好的模型存储起来&#xff0c;这样在进行决策时直接将模型读出&#xff0c;而不需要重新训练模型&#xff0c;这样就大大节约了时间。Python提供的pickle模块就很好地解决了这个问题&#xff0c;它可以序列化对象并保存到磁盘中&…

Linux虚拟内存和物理内存精华【美】

原文地址&#xff1a; 《Playing with Virtual Memory》 http://www.snailinaturtleneck.com/blog/2011/08/30/playing-with-virtual-memory/ 扩展阅读&#xff1a; 《Understanding Memory》 http://www.ualberta.ca/CNS/RESEARCH/LinuxClusters/mem.html 《Understanding Vir…

留不住客户?该从你的系统上找找原因了

本篇文章暨 CSDN《中国 101 计划》系列数字化转型场景之一。 《中国 101 计划——探索企业数字化发展新生态》为 CSDN 联合《新程序员》、GitCode.net 开源代码仓共同策划推出的系列活动&#xff0c;寻访一百零一个数字化转型场景&#xff0c;聚合呈现并开通评选通道&#xff0…

系统配置文件备份比较

客户的系统出各种问题&#xff0c;这次出了问题整整一天都没找出原因&#xff0c;都红脸了&#xff0c;最后发现是系统配置文件被改掉了&#xff0c;简直不能忍&#xff0c;所以写了这个脚本&#xff0c;放到定时任务里面&#xff0c;每天备份比较配置文件import difflib impor…

RPC是什么?为什么要学习RPC?

随着近几年分布式、微服务架构的火热&#xff0c;RPC在开发工作中使用的越来越多&#xff0c;也变的越来越重要。 今天我们来看RPC是什么&#xff0c;为什么要了解RPC,通过学习RPC我们能掌握什么内容&#xff1f; 什么是「RPC」 RPC 全称 Remote Procedure Call, wikipedia的部…

Lua学习笔记6:C++和Lua的相互调用

曾经一直用C写代码。话说近期刚换工作。项目组中的是cocos2dx-lua&#xff0c;各种被虐的非常慘啊有木有。新建cocos2dx-lua项目。打开class能够发现&#xff0c;事实上就是C项目啦&#xff0c;只是为什么仅仅有一类Appdelegate类呢&#xff1f;哈哈,我相信聪明的你一定猜到了&…

Redis消息通知系统的实现

Redis消息通知系统的实现Posted on 2012-02-29by 老王 http://huoding.com/2012/02/29/146最近忙着用Redis实现一个消息通知系统&#xff0c;今天大概总结了一下技术细节&#xff0c;其中演示代码如果没有特殊说明&#xff0c;使用的都是PhpRedis扩展来实现的。内存比如要推送一…

用 Python 实现答题卡识别!

作者 | 棒子胡豆来源丨CSDN博客答题卡素材图片&#xff1a;思路读入图片&#xff0c;做一些预处理工作。进行轮廓检测&#xff0c;然后找到该图片最大的轮廓&#xff0c;就是答题卡部分。进行透视变换&#xff0c;以去除除答题卡外的多余部分&#xff0c;并且可以对答题卡进行校…

Confluence 6 计划任务

管理员控制台能够允许你对 Confluence 运行的计划任务进行计划的调整&#xff0c;这些计划任务将会按照你的调整按时执行。可以按照计划执行的任务如下&#xff1a; Confluence 站点备份存储优化任务&#xff0c;清理 Confluence 的临时目录中的文件和缓存索引优化任务&#xf…

PHP共享内存段

在asp.net和java中都有共享内存&#xff0c;php除了可以使用Memcached等方式变通以外其实php也是支持共享内存的&#xff01;需要安装扩展shmop 找到php安装源文件目录# cd /usr/local/php-5.4.0/ext/shmop # /usr/local/php/bin/phpize # ./configure --with-php-config/usr/l…

马尔科夫随机场的基本概念

1、随机过程&#xff1a; 描写叙述某个空间上粒子的随机运动过程的一种方法。它是一连串随机事件动态关系的定量描写叙述。随机过程与其他数学分支&#xff0c;如微分方程、复变函数等有密切联系。是自然科学、project科学及社会科学等领域研究随机现象的重要工具。 2、马尔科夫…

从事了两年 AI 研究,我学到了什么?

作者 | Tom Silver译者 | 弯月出品 | CSDN我从事人工智能研究的工作已经有两年了&#xff0c;有朋友问我都学到了什么&#xff0c;所以我想借本文分享一些迄今为止积累的经验教训。我将在本文中分享一些常见的经历&#xff0c;还会讨论相对具体的人工智能行业技巧。希望对大家能…

Windows server 2008普通用户不能远程登录问题

1、查登录权限 如果文件服务器没有为用户授权&#xff0c;那么用户自然就不能远程登录服务器系统了&#xff0c;为此笔者决定先仔细检查一下文件服务器系统是否为自己使用的登录账号&#xff0c;授予了远程登录权限。在进行这种检查时&#xff0c;笔者先是在文件服务器本地以系…

面向小白的最全 Python 可视化教程,超全的!

作者 | 俊欣来源丨关于数据分析与可视化今天小编总结归纳了若干个常用的可视化图表&#xff0c;并且通过调用plotly、matplotlib、altair、bokeh和seaborn等模块来分别绘制这些常用的可视化图表&#xff0c;最后无论是绘制可视化的代码&#xff0c;还是会指出来的结果都会通过调…

Atitit.文件搜索工具 attilax 总结

Atitit.文件搜索工具 attilax 总结 1. 指定目录按照体积大小精确搜索1 1.1. File Seeker 4.5 版本的可以&#xff0c;3.5版本的不行。。1 2. 按照文件内容搜索1 2.1. File Seeker2 2.2. Notepad2 2.3. FileLocator Pro 是一款专业的文件搜索软件&#xff0c;2 2.4. 百度硬盘搜索…

ulimit -SHn 65535 含义

linux下用ulimit设置连接数最大值&#xff0c;默认是1024.在高负载下要设置为更高&#xff0c;但最高只能为65535. ulimit只能做临时修改&#xff0c;重启后失效。可以加入ulimit -SHn 65535到 /etc/rc.local 每次启动启用。终极解除 Linux 系统的最大进程数和最大文件打开数限…