php + golang 部分面试题详解

问题

  • PHP
    1. Safe_mode 打开后哪些地方受限?
    2. php7性能为何提高这么高
    3. PHP的垃圾收集机制是怎样的
  • Golang
    1. 阻塞的实现方式有哪些?php

    2. 如何防止data racehtml

    3. Context的原理以及使用场景java

    4. Golang并发原理(协程有什么优点)以及GPM调度策略git

      1. 内存管理以及垃圾回收
       2. Golang的网络IO模型?大量对象初始化如何优化,好比在http 接收数据时bufio的建立
      复制代码

答案

PHP程序员

  1. php7性能为何提高这么高

PHP7革新与性能优化 github

1. error 变为 exception
2. AST 抽象语法树

[php5]  php code-> parser ->opcode -> excute

[php7] php code->parser->ast->opcode->excute

3. JIT  及时编译技术  再opcode 后面增长了类型判断以及及时编译,提高了8倍
4. zval 结构修改  内存从24 降低到16 
5. zend_string 结构修改  val值从 char* 变成[]char 用来下降cpu cache miss
6. hasttable  以前是 buckets 是存储指针 指向各个bucket php7后 将怎么buchkets连在一块,分配在同一快内存
同时bucket 的大小也降低50%
7 函数调用机制 减小重复的指令 再预处理阶段就提早完成某些函数处理 ,提升处理效率
复制代码
  1. PHP的垃圾收集机制是怎样的

PHP 垃圾回收机制详解golang

1. 采用引用计数的方式   每一个zval 都拥有 is_ref 跟ref_count 属性
每增长一次引用  ref_count就会加1
当ref_count = 0时,程序就自动进行gc 
这里的问题在于循环引用会有问题 这个再php5.3以后就已经解决了
解决的方式是
php 会将 ref_count 减一后 还大于0 ,则就会认为他多是一个垃圾

会将这些放入一个垃圾buffer池中。对这个池进行dfs 将每一个节点进行ref_count -1  并标记是不是‘已减’状态

再遍历一次 将全部ref_count 不为1 的变量 清除并回收  改回收动做发送在buffer池满的状况下
复制代码

GOLANG编程

  1. 阻塞的实现方式有哪些

在Golang中各类永远阻塞的姿式数组

1. sync.WaitGroup

var wg sync.WaitGroup
wg.add(1)
wg.wait

2. 空select

3. 空for 循环

4. sync.Mutex
锁上一个已经锁上的锁 会一直阻塞
var m sync.Mutex
m.lock
m.lock

5. os.Signal

信号会一直等待信号的到来

6.空channel

c := make(chan struct{})
<- c
复制代码
  1. 如何防止data race
在代码运行先 先作竞争检测  --race 
若是存在问题就会显示出来 
一般有竞争检测的问题经常发生在 多goroutine 同时修改变量或者map  
通常处理的方式就是加锁

复制代码
  1. Context的原理以及使用场景
context 做用: 用来控制goroutine 的生命周期

type Context interface {
    Deadline()
    Done()
    Err()
    Value(key interface) interface()
}

有这四种方法,因此能够基于如下方式生成context 实例 WithDeadline  WithCancel 等等

场景1   多goroutine  执行超时通知  使用 context.withiTimeout

场景2 函数方法之间传递父子状态,能够实现相似 tracelog 之类的全链路追踪

复制代码
  1. Golang并发原理(协程有什么优点)以及GPM调度策略
golang 并发依照CSP模型,原理是使用goroutine 来传递数据而不是经过加锁的方式来实现数据共享

do not communicate by sharing memory,instead sharing memory by communicating

而主流的编程如java 等 都是用过共享内存的方式实现传递数据的

协程有什么优点: 协程为轻量级线程 
1.为何轻量

线程并发执行流程:
线程是内核对外提供的服务。有内核对其进行调用和切换,线程再等待io操做时 线程会变成unrunnable,进而进行上下文切换。操做系统通常是抢占式调度。上下文切换通常发生在时钟中断或者系统调用返回前
上下文切换  顾名思义  context  switch  先进行context的pop 须要进行save,而后进行switch 因此这一切流程会较为冗长

协程并发执行流程:
不依赖操做系统,golang 本身实现GMP 并发模型。而且协程称为用户态线程,其切换也发生在用户态。用户态并无时钟中断以及系统调用

2 go协程占用内存少

go协程只占用栈内存 大约4~5kb 而一个协程约为1Mb

3 go 声明简单

一个go func 就能直接申明协程 而且可以随意建立上万个协程
复制代码
  1. GPM 调度策略

线程实现模型 缓存

M:内核级线程的封装 G:表明goroutine P: 表明执行go代码片断的上下文环境

  1. go 内存管理
操做系统内存管理:
最初: 内存当作一个数组 每一个元素大小为1B,CPU经过内存地址来选址,取到输入写入寄存器,进而执行机器指令。
此时遇到一个问题(如何处理多任务同时运行)

1. 内存访问冲突,2个程序同时访问同一个内存块 这是产生奔溃

2. 内存不足,由于每一个程序员都须要本身申请内存。可是free 不及时,则会形成内存浪费

3. 程序开发成本高

改进I : 增长虚拟内存 全部程序使用统一的连续虚拟地址,系统会将虚拟地址翻译成真正的物理地址,再加载到内存中

改进II. 对于内存不足的状况  虚拟内存本质是将磁盘当成最终的存储,而内存当作cache,好比申请1G 的空间  其实最终内存不会用到1G的空间,只会开辟很小的一块给程序使用,以避免形成申请过多内存而不使用的浪费,在内存寻址过程当中,发现虚拟内存不指向真正的内存地址时,则会进行再次申请空间  本质就是经常使用的放在内存,不经常使用的放在磁盘中


CPU内存访问全过程
1. CPU使用虚拟内存访问数据,MOV指令加载到寄存器中,将地址传递给MMU
2. MMU生产PTE地址,并尝试从内存中获得他
3. 若是根据PTE地址获取到真实的内存地址,则会正常读取数据,流程结束
4. 若是没法获取到真实的内存地址,则会引发缺页异常
5. 操做系统捕捉到这个异常,会从新生成一页内存,并更新页表
6. 而后回到第三步,流程结束

这里3,4步 就会涉及到一个概念   内存命中率

若是物理内存不足,则会频繁致使3,4步骤,由于磁盘的读取速度缓慢,会致使总体性能降低。cpu指标中的iowait 会很高

这里还会延伸出  CPU cache 的概念

CPU cache 是位于 cpu 与内存之间的缓存 通常有L1,L2,L3 3级缓存,用来调节 cpu 与内存 至 磁盘之间的速度,性能差


GO内存管理:
基于 tcmalloc 实现的内存管理

内存池的概念:

若是不用池的话 
{
malloc  mmap 申请的代价:
1. 系统调用会致使 程序进入内核态,分配完成后,再返回用户态
2. 频繁的申请,会存在大量的内存碎片
}

使用池的优劣
{
    1. 提早申请一大片内存,用户使用的时候不用频繁进入内核态
    2. 由于是连续的空间,不会存在内存碎片的问题
    3. 局部性良好 由于访问的是同一个空间
    
    4.会形成必定的内存浪费,可是对于昂贵资源的申请,则创建资源池是个很是好的实现
}


内存池 mheap

go语言再启动之初 ,就想操做系统那里申请一大块内存用来当作内存池,放入mheap中,

mheap 有如下几个结构

spans(512Mb)  -> bitmap(16Gb)  -> arena(512GB)

各个span 经过链表的方式链接在一块儿 造成各个功能的spans 用sizeclass 做为标识

针对不一样的大小的对象申请内存 使用不一样的sizeclass 作申请 一共有67种

这种链表形式 叫作 mcentral

至于mcache  则是 mcentral 的cache 

什么叫作逃逸分析
c语言中 使用malloc在堆上分配内存 使用free 释放内存
go语言中,编译器自动帮你找到malloc的变量,使用堆内存 这个分析过程就是 逃逸分析
复制代码

如图所示

7 go垃圾回收

go 1.3 以前是采用 mark sweep 
后面采用三色标记法

三色标记法的过程  相似二叉树的层次遍历
1. 将全部待处理的变量表尾白色
2. 从根节点触发 BFS 将可达的 变量放入待处理区, 标记为灰色
3. 将待处理区的节点取出 将其标记为黑色 将其子节点放入待处理区
4. 直到待处理区为空 则全部不可达的变量就是垃圾变量 对其进行清除

在2,3步骤中 程序此时仍然在运行,因此会有新的变量产生,或者旧变量被引用,为了对这块加控制,因此有 write barrier  写屏障的概念  此时会触发stw 

什么时候GC   堆上分配32Kb 内存时就要进行回收检查 主动gc 是阻塞的
复制代码

8 Golang 的网络io模型

网络io模型

阻塞io blocking io

非阻塞io non-blocking io

多路复用io multiplexing io

信号驱动io signal-driven io

异步io asynchronous io

io模型 须要了解的点有如下

用户态 和内核态

用户进程须要进行系统调用的时候 ,必须将其切换成内核态,才能执行包括操做cpu,操做io,外设,网络通讯等等操做

等操做完毕以后,才会切换回用户态,其中包括了创建的大量上下文,这是很是消耗性能的

因此在 用户进程 以及 系统进程之间 会有2个 缓冲区

用户缓冲区 以及 内核缓冲区

而阻塞io 的 核心在于 只有从 内核缓冲区拷贝到用户缓冲区时,才通知用户进程  这就是 阻塞io
而 非阻塞io 是在于   当数据还处于内核缓冲区的时候 就通知用户进程,这就是非阻塞io
非阻塞io 如何通知用户进程呢? 就须要不断的轮询  坏处就是 cpu使用率很低

多路复用io 本质在于 轮询多个非阻塞io,只要有一个任务完成,就发起通知
多路复用io 的实现有如下几个 select  poll 和epoll

纵观全局 每个socket 实际上是non-blocking io的 可是总体用户进程是 blocking 的,使用select 函数进行blocking

信号驱动io 使用sigaction 系统调用,其等待过程是non-blocking 的 这样的cpu利用率高

异步io 与其他4中 同步io 的区别  同步io 是指总有某个步骤会阻塞,而 异步io 永远不会阻塞 再内核准备数据的时候就进行返回

go语言对网络io 的优化

1. 网络库全部都封装成非阻塞方式,减小上下文切换的开销
2. 运行时加入epoll 机制,当goroutine 进行网络io 请求可是 并未就绪时,则挂起G,根据GPM 模型,此时P 会运行其余G,等刚刚的G就绪了,会会从新从就绪队列中取出,再由调度器将他们从新分配M执行
复制代码
  1. 大量对象初始化如何优化,好比在http 接收数据时bufio的建立
优化的方式就是:建立临时对象池
灵感来自于GO的内存模型,基于tcmalloc,在程序开始阶段建立一大块内存来充当对象池
至于建立的内存空间 是来自于 栈 仍是堆 则由go 专门的编译器进行逃逸分析获得的

好处在于  
1. 减小用户态以及内核态之间的上下文切换
2. 减小内存碎片的产生
3. 局部良好性,至关于访问同一个内存空间

复制代码
相关文章
相关标签/搜索