精华内容
下载资源
问答
  • jemalloc 内存分配器 是什么

    万次阅读 2021-08-04 13:39:40
    jemalloc 内存分配器 是什么? 内存池 所谓内存池,是应用程序向操作系统(或 JVM)申请一块内存,自己管理这一块内存,对象的创建和销毁都从这块内存中分配和回收,这么一块内存就可以称作内存池, 对应地,管理...

    jemalloc 内存分配器 是什么?

    内存池
    所谓内存池,是指应用程序向操作系统(或 JVM)申请一块内存,自己管理这一块内存,对象的创建和销毁都从这块内存中分配和回收,这么一块内存就可以称作内存池,

    对应地,管理这块内存的工具就称作内存分配器。
    同时,对于申请对象的不同又可以分为堆内存池和直接内存池,
    1 如果是向 JVM 申请的内存,那就是堆内存池,
    2 如果是向操作系统申请的内存,那就是直接内存池。

    那么,有哪些内存分配器呢?

    业界比较著名的有三个内存分配器:
    1 ptmalloc,Doug Lea 编写的分配器,支持每个线程(per-thread,简称 pt)的 arena,glibc 的默认分配器。
    Doug Lea 大神还有个分配器叫作 dlmalloc,dl 即其名之缩写。
    2 tcmalloc,Google 的分配器,它加入了线程缓存(thread cache,简称 tc),Google 声称其比 ptmalloc 快 6 倍。

    3 jemalloc,Jason Evans 的分配器,je 即其名之缩写,借鉴了很多 tcmalloc 的优秀设计,声称比 tcmalloc 更快,且 CPU 核数越多优势越大,当然,算法也更复杂。

    目前,jemalloc 已经广泛运用在 facebook、Mozilla、FreeBSD 等公司的产品上,那么,它有怎样的优势呢?

    简单总结一下,主要有三大优势:
    1 快速分配和回收
    2 内存碎片少
    3 支持性能分析

    当然了,以上说的都是原生的 jemalloc,我们今天要讲的是 Netty 中的 jemalloc,它是原生 jemalloc 在 Java 中的一种实现方式,并根据 Java 语言自身的特点做了一些删减和优化。

    在这里插入图片描述
    我们先从宏观方面对 Netty 中的内存池有个全面的了解,在 Netty 中,主要包含上面这些组件:

    PoolArena
    PoolChunkList
    PoolChunk
    PoolSubpage
    PoolThreadCache

    PoolArena
    根据内存方式的不同,PoolArena 分成 HeapArena 和 DirectArena 两个子类,在创建 PooledByteBufAllocator 的时候会分别初始化这两种类型的 PoolArena 数组,数组默认大小为核数的 2 倍,同时也会根据可以使用的内存大小动态调整。
    public class PooledByteBufAllocator extends AbstractByteBufAllocator implements ByteBufAllocatorMetricProvider {
    private final PoolArena<byte[]>[] heapArenas;
    private final PoolArena[] directArenas;
    }

    核数也可以通过 JVM 启动参数 io.netty.availableProcessors 配置,因为如果使用低版本的 JDK 且部署在 docker 容器中,获取的是主机的核数,而不是 docker 容器分配的核数。

    PoolArena 中存储着 2 种类型的数据结构,分别为 2 个 PoolSubPage [] 数组和 6 个 PoolChunkList:

    abstract class PoolArena<T> implements PoolArenaMetric {
    	private final PoolSubpage<T>[] tinySubpagePools;
        private final PoolSubpage<T>[] smallSubpagePools;
        private final PoolChunkList<T> q050;
        private final PoolChunkList<T> q025;
        private final PoolChunkList<T> q000;
        private final PoolChunkList<T> qInit;
        private final PoolChunkList<T> q075;
        private final PoolChunkList<T> q100;
    }
    

    为什么这么复杂呢?一切都是为了更好地利用内存。

    实际上,所有的数据都存储在叫作 PoolChunk 的对象中,默认每个 PoolChunk 可以存储 16MB 的数据(chunkSize),每个 PoolChunk 内部又使用伙伴算法将这 16MB 拆分成 2048 个 Page,每个 Page 的大小(pageSize)为 16MB/2048=8KB。

    如果分配的内存(规范化后的内存)小于 8KB,则把 Page 拆分成更小的内存块,并使用 PoolSubpage 管理这些更小的内存,每个 Page 的拆分标准根据这个 Page 首次被分配时的请求的大小决定:

    1 如果小于 512B,则按照 16B 规范化,比如请求的大小为 30B,则规范化到 32B,然后 PoolSubpage 中的元素大小就是 32B,那么,这个 Page 就被拆分成了 8KB/32B=256 个更小的内存块。

    2 如果大于等于 512B,则按照 512B*(2^n) 规范化,比如请求的大小为 996B,那就规范化到 1024B,也就是 1KB,然后这个 Page 就被拆分成了 8KB/1KB=8 个更小的内存块。

    如果分配的内存大于等于 8KB,且小于等于 16MB,则按照 Page 的大小,也就是 8KB,进行规范化,然后再根据伙伴算法的规则进行内存的分配,什么是伙伴算法呢?我们待会讲。
    如果分配的内存大于 16MB,则按照非池化的方式分配内存。
    所以,为了区分以上几种情况,Netty 中定义了一个 SizeClass 类型的枚举,把这几种情况分别叫作 Tiny、Small、Normal、Huge,其中 Huge 不在这个枚举中。
    在这里插入图片描述
    对于 Tiny 和 Small 类型,Netty 为了快速定位,定义了两个数组放在 PoolArena 中,分别是 tinySubpagePools 和 smallSubpagePools,它们的大小分别为 32 和 4,如果这两个数组对应的位置有值,说明之前出现过相同大小的内存块,那就快速定位到那个 PoolSubpage,使用它直接分配内存,而不用再从头查找,加快分配内存的速度。

    前面我们说了,实际上,所有的数据都位于 PoolChunk 中,为了更好地管理这些 PoolChunk,Netty 将它们以双向链表的形式存储在 PoolChunkList 中,同时 PoolChunkList 本身也以双向链表的形式呈现。

    在 PoolArena 中,定义了 6 个 PoolChunkList,分别是 qInit、q000、q025、q050、q075、q100,Netty 根据 PoolChunk 的使用率将它们放到不同类型的 PoolChunkList 中,它们代表的使用率分别为:

    qInit,内存使用率为 Integer.MIN_VALUE ~ 25%,当然不可能有负的使用率,所以最小应该是 0
    q000,内存使用率为 0 ~ 50%
    q025,内存使用率为 25% ~ 75%
    q050,内存使用率为 50% ~ 100%
    q075,内存使用率为 75% ~ 100%
    q100,内存使用率为 100% ~ Integer.MAX_VALUE,当然不可能有超过 100% 的使用率,所以最大应该是 100%

    举个例子来说明,比如一个 Chunk 首次分配了大小为 512B 的内存,那么它的内存使用率就是 512B/16MB 不足 1%,向上取整为 1%,初始时放在 qInit 中,当其分配的总内存超过了 4MB 的时候,也就是达到 25% 了,这个 PoolChunk 就被移动到 q000 中,同样地,当其分配的内存超过 8MB 的时候,就移动到了 q025 中。反过来也是一样,当有对象释放内存时,这部分内存又会被回收到 PoolChunk 中待分配,这时候内存使用会降低,当降低到 4MB 时,也就是 q025 的下限,则会将这个 PoolChunk 移动到 q000 中。

    PoolChunkList

    正如前面所说,PoolChunkList 就是相近内存使用率的 PoolChunk 的集合,这些 PoolChunk 以双链表的形式存储在 PoolChunkList 中,而 PoolChunkList 本身也以双向链表的形式连在一起,为什么要以双向链表的形式存在呢?
    在这里插入图片描述
    其实,这包含两个问题:
    1 PoolChunk 以双向链表的形式存在,是为了删除元素(移动 PoolChunk)的时候更快,比如,要删除 chunk2,只要把它的 prev 和 next 连一起就行了,时间复杂度更低;
    2 PoolChunkList 以双向链表的形式存在,是为了让 PoolChunk 在 PoolChunkList 之间移动更快,比如,一个 PoolChunk 不管是从 q025 到 q050,还是从 q050 回到 q025,都很快,时间复杂度都很低;

    另外,在 Netty 中,当分配内存时,优先从 q050 中寻找合适的 PoolChunk 来分配内存,为什么先从 q050 开始呢?

    private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
        if (q050.allocate(buf, reqCapacity, normCapacity) 
            || q025.allocate(buf, reqCapacity, normCapacity) 
            || q000.allocate(buf, reqCapacity, normCapacity) 
            || qInit.allocate(buf, reqCapacity, normCapacity) 
            || q075.allocate(buf, reqCapacity, normCapacity)) {
            return;
        }
        // 省略其它代码
    }
    

    因为 q050 中的 PoolChunk 的内存使用率都比 50% 多一点,这样更容易找到符合条件的 PoolChunk,又不至于使 PoolChunk 的利用率偏低。

    因为 q050 中的 PoolChunk 的内存使用率都比 50% 多一点,这样更容易找到符合条件的 PoolChunk,又不至于使 PoolChunk 的利用率偏低。

    我们举个例子,假如从 q075 中先寻找,如果要分配 4M 以上的内存就无法找到合适的 PoolChunk;假如从 q025 中先寻找,可能正好有内存使用率在 25% 以上的 PoolChunk,这时候就直接使用了,那么 q050 中的 PoolChunk 就很难被利用起来,也就是 q050 中的 PoolChunk 的剩余空间很难被利用起来,进而导致整体的利用率偏低,也就是内存碎片会变高。

    那么,如果先从 q050 寻找合适的 PoolChunk 呢?这时 q025 和 q075 中的 PoolChunk 可能永远都不会被使用到,不过没关系,对于 q025 中的 PoolChunk 的内存使用率变为 0 的时候,它们自然就被释放了,而 q075 中的 PoolChunk 本身内存使用率就已经很高了,不用到它们反而更好,等它们的内存使用率降低的时候就又回到 q050 中了,此时就又来很容易地被利用起来。

    因此,从 q050 开始寻找,能很大程度上增大整体的内存使用率,降低内存碎片的存在。

    PoolChunk
    前面我们说了,默认地,一个 PoolChunk 可以存储 16MB 的数据,PoolChunk 是真正存储数据的地方,何以见得?

    final class PoolChunk<T> implements PoolChunkMetric {
        // 数据存储的地方
        final T memory;
        // 满二叉树对应节点是否被分配,数组大小为4096
        private final byte[] memoryMap;
        // 满二叉树原始节点高度,数组大小为4096
        private final byte[] depthMap;
        // 管理更小的内存,数组大小为2048
        private final PoolSubpage<T>[] subpages;
        // 剩余的内存
        private int freeBytes;
        PoolChunk<T> prev;
        PoolChunk<T> next;
    }
    

    PoolChunk 本身是一个泛型类型,内部保存了一个叫作 memory 的变量,这个 memory 会根据分配的是堆内存还是直接内存而变换类型:
    对于堆内存,memory 的类型为 byte []
    对于直接内存,memory 的类型为 ByteBuffer,实际上为 DirectByteBuffer。

    所有的数据都存储在 memory 中,至于更小粒度的划分,比如 PoolSubpage,它们使用各种偏移量对 memory 进行分段处理,数据本身并不会复制到这些细粒度的类中。

    Netty 中,并没有 PoolPage 或者 Page 这个类,Page 是一种抽象的说法,
    它表示的是 PoolChunk 中每 8KB 的数据块,它同样使用 PoolSubpage 来表示。
    

    默认地,Netty 使用伙伴算法将 PoolChunk 分成 2048 个 Page,这些 Page 又向上形成一颗满二叉树:
    在这里插入图片描述
    结合上图,我们先来简单介绍一下 PoolChunk 中的几个变量:

    depthMap,保存着满二叉树原始的高度信息,比如 depthMap [1024]=10

    memoryMap,初始值等于 depthMap,随着节点的被分配,它的值会不断变化,更新子节点的值时,会同时更新其父节点的值,其父节点的值等于两个子节点值中的最小者。

    subpages,对应于上图中的 Page0、Page1、…、Page2047,在 Netty 中并没有 Page 的具体代码实现,它同样使用 PoolSubpage 来表示。只有分配的内存小于 8KB,才会使用 PoolSubpage 进行管理,在 PoolSubpage 创建之后,会加入到 PoolArena 中 tinySubpagePools [] 或 smallSubpagePools [] 对应位置的链表中,同时,在 PoolSubpage 代表的内存被分配完之后,会从对应的链表中删除,也就是说,在同一时刻,head 最多只会与一个 PoolSubpage 形成双向链表。

    freeBytes,PoolChunk 中剩余的内存,即可被使用的内存。

    如果分配的内存大于等于 8KB,由 PoolChunk 自己管理。

    为了更好地理解伙伴分配算法,我们来假想一种分配内存的情况,如果分配内存的顺序分别为 8KB、16KB、8KB,则会按以下顺序进行:

    8KB,符合一个 Page 大小,所以从第 11 层(12-8KB/8KB)寻找节点,这里找到了 2048 这个节点,发现其 memoryMap [2048]=11=depthMap [2048],可以被分配,然后到其对应的 Page [0] 中分配内存,分配之后将其 memoryMap [2048]=12,memoryMap [1024]=11=(2048 和 2049 中的最小者 11),memoryMap [512]=10=(1024 和 1025 中的最小者 10),…,memoryMap [1]=1;

    16KB,符合两个 Page 大小,所以从第 10 层寻找节点(12-16KB/8KB),找到 1024 节点,发现其 memoryMap [1024]=11!=depthMap [1024],不符合条件,继续寻找到 1025 节点,发现其 memoryMap [1025]=10=depthMap [1025],符合条件,所以,到其对应的叶子节点 2050/2051 对应的 Page [2]/Page [3] 中分配内存,分配之后 memoryMap [2050]=12,memoryMap [2051]=12,memoryMap [1025]=12=(2050 和 2051 中的最小值 12),memoryMap [512]=11=(1024 和 1025 中的最小者 11),…,memoryMap [1]=1;

    8KB,符合一个 Page 大小,所以从第 11 层(12-8KB/8KB)寻找节点,2048 已经不符合条件了,所以找到了 2049 这个节点,到其对应的 Page [1] 中分配内存,然后更新 memoryMap [2049]=12,memoryMap [1024]=12=(2048 和 2049 中的最小者 12),memoryMap [512]=12=(1024 和 1025 中的最小者 12),…,memoryMap [1]=1;

    至此,三次内存都分配完毕,总共分配了 Page0~Page3 共 4 个 Page,从分配结果也可以看出,使用伙伴分配算法,能极大地保证分配连续的内存空间,并减少内存碎片的诞生。

    PoolSubpage
    前面我们说过,只有当分配的内存小于一个 Page 大小,即 8KB 时,才会使用 PoolSubpage 来进行管理,那么它是怎么管理的呢?

    让我们先来看看它的几个关键字段:

    final class PoolSubpage<T> implements PoolSubpageMetric {
        // 对应满二叉树中的哪个节点
        private final int memoryMapIdx;
        // 在PoolChunk的memory中的偏移量
        private final int runOffset;
        // 表示每个小块的状态
        private final long[] bitmap;
        // 每个小块(元素)的大小
        int elemSize;
        // 最大的元素个数=8KB/elemSize
        private int maxNumElems;
        // 需要使用到几个long
        private int bitmapLength;
        // 可用的元素个数
        private int numAvail;
        // 双向链表的指针
        // 与PoolArena中的tinySubpagePoos或smallSubpagePools中的元素形成双向链表
        PoolSubpage<T> prev;
        PoolSubpage<T> next;
    }
    

    elemSize 表示每个元素的大小,这个大小是根据这个 Page 接收到的第一个请求的大小决定的。
    比如,首次分配 30B 的内存,则会经历以下几个步骤:

    1判断小于 512B,按 16B 向上规范化到 32B;
    2 在满二叉树的第 11 层寻找一个可用的节点,假如是 2049,即 memoryMapIdx=2049,它代表的是 Page1,Page1 这个节点在 PoolChunk 中对应到 memory 上的偏移量就是 8192(前面有个 Page0),所以,runOffset=8192;
    3 此时,会把 Page0 按 32B 分成(8KB/32B=256)个小块,所以,elemSize=32B,maxNumElems=256,numAvail=256;
    4 同时,这 256 个小块就需要 256 个 bit(位)来表示其每个小块的状态,也就是需要(256/64=4)个 long 类型来表示,所以,bitmapLength=4;
    5 然后,把这个 PoolSubpage 与 PoolArena 的 tinySubpagePools [1](相当于 head)形成双向链表,因为 tinySubpagePools [0] 代表的是 16B 的内存,tinySubpagePools [1] 代表的是 32B 的内存;
    6 当分配完这 32B 之后,可用节点数减一,所以,numAvail=255;

    当再次分配规范为 32B 内存的时候,就看 PoolArena 的 tinySubpagePools [1] 的 next 中有没有值,有值,就直接使用其分配内存了,而不用再重新走一遍上面的过程,从而加快分配内存的速度。

    PoolThreadCache
    前面讲了这么多,分配内存的速度已经足够快了,但是,还可以更快,那就是加入线程缓存 PoolThreadCache,那么,PoolThreadCache 在何时使用呢?

    其实,这要结合回收内存一起使用,当回收内存时,先不还给 PoolChunk,而是使用本地线程缓存起来,当下一次再分配同样大小(规范化后的大小)的内存时,先尝试从本地线程缓存里面取,如果取到了就可以直接使用了。

    那么,PoolThreadCache 可以缓存哪些类型的缓存呢?

    在 Netty 中,除了 Huge,其它类型的内存都可以缓存,即 Tiny、Small、Normal,当然,根据堆内存和直接内存的不同,PoolThreadCache 中又分成了两大类:

    final class PoolThreadCache {
        // 堆内存的缓存
        private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
        private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
        private final MemoryRegionCache<byte[]>[] normalHeapCaches;
        // 直接内存的缓存
        private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
        private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
        private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
    }
    

    PoolThreadCache 中使用了一个叫作 MemoryRegionCache 的类来做缓存,它内部维护了一个队列,当回收内存时,这块内存进入到这个队列中,当下次再分配同样大小(规范化后的大小)的内存时,从这个队列中取,关于 PoolThreadCache 的使用,我们下一节结合代码一起学习。

    展开全文
  • 成本归集与分配

    万次阅读 2019-04-23 20:32:42
    1、成本归集:通过一定的会计制度以有序的方式进行成本数据的收集或汇总。例如制造费用按车间归集。 2、成本分配:将归集的间接成本分配给成本对象,也叫间接费用的分摊。 3、成本的核算账户设置 成本核算...

    一、归集与分配概念

      1、成本归集:通过一定的会计制度以有序的方式进行成本数据的收集或汇总。例如制造费用按车间归集。

      2、成本分配:将归集的间接成本分配给成本对象,也叫间接费用的分摊。

      3、成本的核算账户设置

        成本核算账户包括:基本生产成本(总账与明细账)、辅助生产成本、制造费用

    二、生产的归集与分配

      1、材料的归集与分配:材料费用包括企业在生产经营过程中实际消耗的各种原材料及主要材料、辅助材料、外购半成品等

            基本生产车间发生的直接用于产品生产的材料成本,直接计入该产品成本“直接材料”成本项目中;

            如果是几种产品共同耗用的材料成本,则应采用适当的分配方法,分配计入各有关产品成本的“直接材料”成本项目;

            依据用途分别归入直接材料、制造费用、管理费用。

       2、材料费用计算步骤:

          (1)  计算各种产品的材料定额消耗量或材料定额成本:材料定额消耗量=产品投产量*单位消耗定额

          (2)计算材料费用分配率:材料费用分配率=材料实际总消耗量/各种产品材料定额消耗量之和

          (3)  计算各种产品应负担的材料费用:某种产品应分配的材料数量=该产品的材料定额消耗量*分配率

      3、人工费用核算:

          (1)人工费用包括工资和福利费用

          (2)人工归集与分配

      3、辅助生产费用的归集与分配

          (1)辅助生产:为基本生产车间服务而进行的产品生产和劳务供应,主要任务是为企业内部的生产和管理服务

          (2)辅助生产成本的归集:辅助生产成本账户

                   发生直接成本——”辅助生产成本“账户

                   发生制造费用——”制造费用“账户——”辅助生产成本“账户

         

       (3)归集程序:

                发生直接成本时(直接材料、直接人工):借(辅助生产成本——XX车间) 贷(原材料/应付职工薪酬)

                发生制造费用(折旧费、水电费等)时:借(制造费用)  贷(累计折旧/银行存款/周转材料等)

               PS:如果辅助生产车间不对外提供商品,且规模较小,制造费用少,也可直接计入“辅助生产成本”账户及明细账。

      (4)辅助生产费用的分配:

                按照收益原则将辅助生产车间为提供产品或提供劳务而发生的消耗转嫁给使用产品或接受劳务的其他部门。

                产品性生产:账务处理同基本生产车间生产产品一样

                劳务性生产:按收益原则,在各受益单位之间进行分配

                分配方法: 直接分配法(不考虑辅助生产车间之间相互提供产品或劳务的情况,而是将各种辅助生产费用直接分配给辅助生产车间以外的收益单位,忽略辅助车间之间的业务往来,计算便利,但影响计算完整性适宜在辅助生产内部相互提供劳务、作业不多,不进行费用的交互分配,对辅助生产成本和企业产品成本影响不大的情况下采用)

                顺序分配法(按辅助车间收益的多少排列先后顺序,受益少的排在前面,按此顺序分配费用的方法,排在前面的辅助生产车间不承担后一个车间的费用,后者的分配额=其直接费用+前者分配来的费用,修正直接分配法的不足,关键是确定费用分配顺序)

               交互分配法(首先根据各辅助生产车间相互提供劳务的数量及交互分配前的劳务单位成本/计划单位成本在各辅助生产车间之间进行一次交互分配,然后将各辅助生产车间交互分配后的实际费用,按照对辅助生产生产车间以外部门提供劳务的数量及交互分配后的单位成本,分配给辅助生产车间以外的各受益部门,即先在各辅助生产车间之间进行分配,再对除辅助生产以外的受益对象进行分配,计算量大,且各辅助车间的劳务结算只能等其他辅助车间费用转入后才能计算实际费用,影响成本分配的及时性,适用于各辅助生产费用水平相差不大的企业)

               计划分配法(按产品或者劳务的计划单位成本和实际耗用数量进行辅助生产费用分配的一种方法,只适用于在辅助生产计划单位成本制定得比较准确的情况下采用)

              代数分配法(先根据解联立方程的原理,计算辅助生产产品或劳务的单位成本,然后根据各受益单位耗用的数量和单位成本分配辅助生产费用的一种方法,计算精确但比较麻烦,适合辅助生产车间较少或会计工作实现了电算化的企业采用)

      4、制造费用的归集与分配

           (1)制造费用的内容:包括企业生产产品、提供劳务而发生的各项,主要有工资和福利费、机物料消耗、折旧费等

           (2)制造费用的归集:“制造费用”

           (3)制造费用的分配:实际分配率分配法(当期制造费用,存在因果关系)、预计分配率分配法(按年度制造费用预算总额、年度计划产量定额工时)

      5、其他费用的归集与分配

         (1)预提和摊销费用:

                  预提指固定资产大修费、季节性大修停工损失、财产保险费;

                  摊销指经营性租入固定资产的租金、预付保险费

         (2)生产损失:

                  废品损失指不可修复废品的生产成本+可修复的废品的修复费用;

                  停工损失:停电、供料不足、机器设备发生故障(停工损失);

                                    季节性、大修理期间的停工损失(制造费用);

                                    非常原因引起的停工损失、由于计划安排减产(营业外支出);

                                    应向责任人索赔的停工损失(其他应收款);

        (3)其他费用:利息、税金等

    三、完工产品和在产品成本的分配

      1、在产品及其数量的核算

          (1)在产品含义:企业的在产品是指没有完成全部生产过程,不能作为商品销售的产品

                   狭义:在工厂车间内进行加工的在制品,以及正在返修的废品和已完成本车间生产,但尚未验收入库的产品(某个车                           间、某个步骤而言)

                   广义:不仅包括狭义在产品,还包括已经完成部分加工阶段,已由中间仓库验收,但还需继续加工的半成品,以及等                           待返修的废品。(整个企业而言)

        (2)在产品的核算:对在产品实物数量的核算,主要包括在产品收发结存的日常核算和在产品的清查两项工作;产品数量的               核算对于正确计算产品成本,加强生产资金管理和保护企业财产的安全,都具有十分重要的意义。

      2、完工产品与在产品成本的分配方法

          企业应根据月末结存在产品数量的多少,月末在产品数量变化的大小,月末在产品价值的大小,在产品成本中各项费用比重    的大小,以及企业定额管理基础工作的扎实与否等方面的因素,采用适当的分配方法。

        (1)在产品与完工产品成本的计算模式:

                 本月完工产品成本=月初在产品成本+本月生产费用-月末在产品成本

        (2)在产品成本的计算:

          在产品不计价法:不计算月末在产品成本,即月末在产品成本为零。适用于各月末在产品数量很少,价值很低,且各月在产               品数量稳定。即下月初在产品成本为零,则简化为:本期生产费用=本期完工产品成本

          在产品成本按其年初为固定数计算法:平时月末在产品固定按年初成本计价的方法,年末则根据实地盘点结果确定在产品成               本。适用于各月末在产品数量较少,或者虽然在产品数量较多,但各月末在产品数量变化不大,在这种情况下,月初、               月末在产品成本的差额对完工产品成本影响不大。采用这种方法时,对每年年终的在产品成本,必须根据年终实际盘点               的在产品数量重新计算,以保证产品成本的相对准确性。1-11月计算:本期生产费用=本期完工产品成本                                       12月末计算:本期完工产品成本=期初在产品成本+本期生产费-期末盘点确定在产品成本

         在产品成本按所耗原材料费用计算法:在产品按原材料费用计算,产品的加工费用全部由完工产品负担。适用于各月在产                   品数量变化大,且原材料费用在产品成本中所占费用比重较大。即:本期完工产品成本=材料+人工+制造费用;本期在                 产品成本=材料

         约当产量比例法:将月末在产品的数量按其完工程度折算为相当于完工产品的数量,即约当产量,并将本期产品生产费用按                照完工产品数量和月末在产品的约当产量比例进行分配,计算出完工产品成本和月末在产品成本的方法。适用于月末在                产品数量较多,各月间在产品数量变动较大,产品成本中原材料费用与其他各项费用所占比重相差不大的产品。

        在产品按定额成本计算法:月末在产品以定额成本计价,将其从月初在产品定额成本与本月生产费用之和中扣除,余额即为本             月完工产品成本。即,每月生产费用脱离定额的差异,全部计入当月完工产品成本。适用于定额管理工作较好,各项消耗             定额比较准确、稳定,而且各月末在产品结存数量比较稳定的产品。

                                                                              完工产品成本=月初在产品费用+本月生产费用-月末在产品定额成本

           定额比例法:按完工产品与在产品的定额耗用量(或定额成本)的比例分配生产费用。适用于定额管理基础较好,各项消耗                定额比较健全、稳定,各月末在产品数量变动较大的产品。

           在产品按完工产品计算法:将在产品视同完工产品分配费用的方法。适用于月末在产品以及接近完工,或产品已经加工完                    毕,但尚未验收入库的产品。(与约当产量计算法基本相同,只是程度按100%进行计算)

     

             

     

        

     

     

     

             

               

     

     

      

      

    展开全文
  • Windows内存分配(转)

    千次阅读 2011-01-15 06:15:00
    注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。 3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的

    堆和栈的区别

    一、预备知识—程序的内存分配

    一个由C/C++编译的程序占用的内存分为以下几个部分

    1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

    2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。

    3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放

    4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放

    5、程序代码区—存放函数体的二进制代码。

    二、例子程序

    这是一个前辈写的,非常详细

    //main.cpp

    int a = 0; 全局初始化区

    char *p1; 全局未初始化区

    main()

    {

    int b; 栈

    char s[] = "abc"; 栈

    char *p2; 栈

    char *p3 = "123456"; 123456/0在常量区,p3在栈上。

    static int c =0; 全局(静态)初始化区

    p1 = (char *)malloc(10);

    p2 = (char *)malloc(20);

    分配得来得10和20字节的区域就在堆区。

    strcpy(p1, "123456"); 123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。

    }

    二、堆和栈的理论知识

    2.1申请方式

    stack:

    由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间

    heap:

    需要程序员自己申请,并指明大小,在c中malloc函数

    如p1 = (char *)malloc(10);

    在C++中用new运算符

    如p2 = (char *)malloc(10);

    但是注意p1、p2本身是在栈中的。

    2.2

    申请后系统的响应

    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

    堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,

    会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

    2.3申请大小的限制

    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

    2.4申请效率的比较:

    栈由系统自动分配,速度较快。但程序员是无法控制的。

    堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.

    另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

    2.5堆和栈中的存储内容

    栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

    当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

    2.6存取效率的比较

    char s1[] = "aaaaaaaaaaaaaaa";

    char *s2 = "bbbbbbbbbbbbbbbbb";

    aaaaaaaaaaa是在运行时刻赋值的;

    而bbbbbbbbbbb是在编译时就确定的;

    但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。

    比如:

    #include

    void main()

    {

    char a = 1;

    char c[] = "1234567890";

    char *p ="1234567890";

    a = c[1];

    a = p[1];

    return;

    }

    对应的汇编代码

    10: a = c[1];

    00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]

    0040106A 88 4D FC mov byte ptr [ebp-4],cl

    11: a = p[1];

    0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]

    00401070 8A 42 01 mov al,byte ptr [edx+1]

    00401073 88 45 FC mov byte ptr [ebp-4],al

    第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。

    2.7小结:

    堆和栈的区别可以用如下的比喻来看出:

    使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。

    使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

    windows进程中的内存结构

    在阅读本文之前,如果你连堆栈是什么多不知道的话,请先阅读文章后面的基础知识。

    接触过编程的人都知道,高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢?程序又是如何使用这些变量的呢?下面就会对此进行深入的讨论。下文中的C语言代码如没有特别声明,默认都使用VC编译的release版。

    首先,来了解一下 C 语言的变量是如何在内存分部的。C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码:

    #include

    int g1=0, g2=0, g3=0;

    int main()

    {

    static int s1=0, s2=0, s3=0;

    int v1=0, v2=0, v3=0;

    //打印出各个变量的内存地址

    printf("0x%08x/n",&v1); //打印各本地变量的内存地址

    printf("0x%08x/n",&v2);

    printf("0x%08x/n/n",&v3);

    printf("0x%08x/n",&g1); //打印各全局变量的内存地址

    printf("0x%08x/n",&g2);

    printf("0x%08x/n/n",&g3);

    printf("0x%08x/n",&s1); //打印各静态变量的内存地址

    printf("0x%08x/n",&s2);

    printf("0x%08x/n/n",&s3);

    return 0;

    }

    编译后的执行结果是:

    0x0012ff78

    0x0012ff7c

    0x0012ff80

    0x004068d0

    0x004068d4

    0x004068d8

    0x004068dc

    0x004068e0

    0x004068e4

    输出的结果就是变量的内存地址。其中v1,v2,v3是本地变量,g1,g2,g3是全局变量,s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的,但是本地变量和全局变量分配的内存地址差了十万八千里,而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。

    ├———————┤低端内存区域

    │ …… │

    ├———————┤

    │ 动态数据区 │

    ├———————┤

    │ …… │

    ├———————┤

    │ 代码区 │

    ├———————┤

    │ 静态数据区 │

    ├———————┤

    │ …… │

    ├———————┤高端内存区域

    堆栈是一个先进后出的数据结构,栈顶地址总是小于等于栈的基地址。我们可以先了解一下函数调用的过程,以便对堆栈在程序中的作用有更深入的了解。不同的语言有不同的函数调用规定,这些因素有参数的压入规则和堆栈的平衡。windows API的调用规则和ANSI C的函数调用规则是不一样的,前者由被调函数调整堆栈,后者由调用者调整堆栈。两者通过“__stdcall”和“__cdecl”前缀区分。先看下面这段代码:

    #include

    void __stdcall func(int param1,int param2,int param3)

    {

    int var1=param1;

    int var2=param2;

    int var3=param3;

    printf("0x%08x/n",&m1); //打印出各个变量的内存地址

    printf("0x%08x/n",&m2);

    printf("0x%08x/n/n",&m3);

    printf("0x%08x/n",&var1);

    printf("0x%08x/n",&var2);

    printf("0x%08x/n/n",&var3);

    return;

    }

    int main()

    {

    func(1,2,3);

    return 0;

    }

    编译后的执行结果是:

    0x0012ff78

    0x0012ff7c

    0x0012ff80

    0x0012ff68

    0x0012ff6c

    0x0012ff70

    ├———————┤<—函数执行时的栈顶(ESP)、低端内存区域

    │ …… │

    ├———————┤

    │ var 1 │

    ├———————┤

    │ var 2 │

    ├———————┤

    │ var 3 │

    ├———————┤

    │ RET │

    ├———————┤<—“__cdecl”函数返回后的栈顶(ESP)

    │ parameter 1 │

    ├———————┤

    │ parameter 2 │

    ├———————┤

    │ parameter 3 │

    ├———————┤<—“__stdcall”函数返回后的栈顶(ESP)

    │ …… │

    ├———————┤<—栈底(基地址 EBP)、高端内存区域

    上图就是函数调用过程中堆栈的样子了。首先,三个参数以从又到左的次序压入堆栈,先压“param3”,再压“param2”,最后压入“param1”;然后压入函数的返回地址(RET),接着跳转到函数地址接着执行(这里要补充一点,介绍UNIX下的缓冲溢出原理的文章中都提到在压入RET后,继续压入当前EBP,然后用当前ESP代替EBP。然而,有一篇介绍windows下函数调用的文章中说,在windows下的函数调用也有这一步骤,但根据我的实际调试,并未发现这一步,这还可以从param3和var1之间只有4字节的间隙这点看出来);第三步,将栈顶(ESP)减去一个数,为本地变量分配内存空间,上例中是减去12字节(ESP=ESP-3*4,每个int变量占用4个字节);接着就初始化本地变量的内存空间。由于“__stdcall”调用由被调函数调整堆栈,所以在函数返回前要恢复堆栈,先回收本地变量占用的内存(ESP=ESP+3*4),然后取出返回地址,填入EIP寄存器,回收先前压入参数占用的内存(ESP=ESP+3*4),继续执行调用者的代码。参见下列汇编代码:

    ;--------------func 函数的汇编代码-------------------

    :00401000 83EC0C sub esp, 0000000C //创建本地变量的内存空间

    :00401003 8B442410 mov eax, dword ptr [esp+10]

    :00401007 8B4C2414 mov ecx, dword ptr [esp+14]

    :0040100B 8B542418 mov edx, dword ptr [esp+18]

    :0040100F 89442400 mov dword ptr [esp], eax

    :00401013 8D442410 lea eax, dword ptr [esp+10]

    :00401017 894C2404 mov dword ptr [esp+04], ecx

    ……………………(省略若干代码)

    :00401075 83C43C add esp, 0000003C ;恢复堆栈,回收本地变量的内存空间

    :00401078 C3 ret 000C ;函数返回,恢复参数占用的内存空间

    ;如果是“__cdecl”的话,这里是“ret”,堆栈将由调用者恢复

    ;-------------------函数结束-------------------------

    ;--------------主程序调用func函数的代码--------------

    :00401080 6A03 push 00000003 //压入参数param3

    :00401082 6A02 push 00000002 //压入参数param2

    :00401084 6A01 push 00000001 //压入参数param1

    :00401086 E875FFFFFF call 00401000 //调用func函数

    ;如果是“__cdecl”的话,将在这里恢复堆栈,“add esp, 0000000C”

    聪明的读者看到这里,差不多就明白缓冲溢出的原理了。先来看下面的代码:

    #include

    #include

    void __stdcall func()

    {

    char lpBuff[8]="/0";

    strcat(lpBuff,"AAAAAAAAAAA");

    return;

    }

    int main()

    {

    func();

    return 0;

    }

    编译后执行一下回怎么样?哈,“"0x00414141"指令引用的"0x00000000"内存。该内存不能为"read"。”,“非法操作”喽!"41"就是"A"的16进制的ASCII码了,那明显就是strcat这句出的问题了。"lpBuff"的大小只有8字节,算进结尾的/0,那strcat最多只能写入7个"A",但程序实际写入了11个"A"外加1个/0。再来看看上面那幅图,多出来的4个字节正好覆盖了RET的所在的内存空间,导致函数返回到一个错误的内存地址,执行了错误的指令。如果能精心构造这个字符串,使它分成三部分,前一部份仅仅是填充的无意义数据以达到溢出的目的,接着是一个覆盖RET的数据,紧接着是一段shellcode,那只要着个RET地址能指向这段shellcode的第一个指令,那函数返回时就能执行shellcode了。但是软件的不同版本和不同的运行环境都可能影响这段shellcode在内存中的位置,那么要构造这个RET是十分困难的。一般都在RET和shellcode之间填充大量的NOP指令,使得exploit有更强的通用性。

    ├———————┤<—低端内存区域

    │ …… │

    ├———————┤<—由exploit填入数据的开始

    │ │

    │ buffer │<—填入无用的数据

    │ │

    ├———————┤

    │ RET │<—指向shellcode,或NOP指令的范围

    ├———————┤

    │ NOP │

    │ …… │<—填入的NOP指令,是RET可指向的范围

    │ NOP │

    ├———————┤

    │ │

    │ shellcode │

    │ │

    ├———————┤<—由exploit填入数据的结束

    │ …… │

    ├———————┤<—高端内存区域

    windows下的动态数据除了可存放在栈中,还可以存放在堆中。了解C++的朋友都知道,C++可以使用new关键字来动态分配内存。来看下面的C++代码:

    #include

    #include

    #include

    void func()

    {

    char *buffer=new char[128];

    char bufflocal[128];

    static char buffstatic[128];

    printf("0x%08x/n",buffer); //打印堆中变量的内存地址

    printf("0x%08x/n",bufflocal); //打印本地变量的内存地址

    printf("0x%08x/n",buffstatic); //打印静态变量的内存地址

    }

    void main()

    {

    func();

    return;

    }

    程序执行结果为:

    0x004107d0

    0x0012ff04

    0x004068c0

    可以发现用new关键字分配的内存即不在栈中,也不在静态数据区。VC编译器是通过windows下的“堆(heap)”来实现new关键字的内存动态分配。在讲“堆”之前,先来了解一下和“堆”有关的几个API函数:

    HeapAlloc 在堆中申请内存空间

    HeapCreate 创建一个新的堆对象

    HeapDestroy 销毁一个堆对象

    HeapFree 释放申请的内存

    HeapWalk 枚举堆对象的所有内存块

    GetProcessHeap 取得进程的默认堆对象

    GetProcessHeaps 取得进程所有的堆对象

    LocalAlloc

    GlobalAlloc

    当进程初始化时,系统会自动为进程创建一个默认堆,这个堆默认所占内存的大小为1M。堆对象由系统进行管理,它在内存中以链式结构存在。通过下面的代码可以通过堆动态申请内存空间:

    HANDLE hHeap=GetProcessHeap();

    char *buff=HeapAlloc(hHeap,0,8);

    其中hHeap是堆对象的句柄,buff是指向申请的内存空间的地址。那这个hHeap究竟是什么呢?它的值有什么意义吗?看看下面这段代码吧:

    #pragma comment(linker,"/entry:main") //定义程序的入口

    #include

    _CRTIMP int (__cdecl *printf)(const char *, ...); //定义STL函数printf

    /*---------------------------------------------------------------------------

    写到这里,我们顺便来复习一下前面所讲的知识:

    (*注)printf函数是C语言的标准函数库中函数,VC的标准函数库由msvcrt.dll模块实现。

    由函数定义可见,printf的参数个数是可变的,函数内部无法预先知道调用者压入的参数个数,函数只能通过分析第一个参数字符串的格式来获得压入参数的信息,由于这里参数的个数是动态的,所以必须由调用者来平衡堆栈,这里便使用了__cdecl调用规则。BTW,Windows系统的API函数基本上是__stdcall调用形式,只有一个API例外,那就是wsprintf,它使用__cdecl调用规则,同printf函数一样,这是由于它的参数个数是可变的缘故。

    ---------------------------------------------------------------------------*/

    void main()

    {

    HANDLE hHeap=GetProcessHeap();

    char *buff=HeapAlloc(hHeap,0,0x10);

    char *buff2=HeapAlloc(hHeap,0,0x10);

    HMODULE hMsvcrt=LoadLibrary("msvcrt.dll");

    printf=(void *)GetProcAddress(hMsvcrt,"printf");

    printf("0x%08x/n",hHeap);

    printf("0x%08x/n",buff);

    printf("0x%08x/n/n",buff2);

    }

    执行结果为:

    0x00130000

    0x00133100

    0x00133118

    hHeap的值怎么和那个buff的值那么接近呢?其实hHeap这个句柄就是指向HEAP首部的地址。在进程的用户区存着一个叫PEB(进程环境块)的结构,这个结构中存放着一些有关进程的重要信息,其中在PEB首地址偏移0x18处存放的ProcessHeap就是进程默认堆的地址,而偏移0x90处存放了指向进程所有堆的地址列表的指针。windows有很多API都使用进程的默认堆来存放动态数据,如windows 2000下的所有ANSI版本的函数都是在默认堆中申请内存来转换ANSI字符串到Unicode字符串的。对一个堆的访问是顺序进行的,同一时刻只能有一个线程访问堆中的数据,当多个线程同时有访问要求时,只能排队等待,这样便造成程序执行效率下降。

    最后来说说内存中的数据对齐。所位数据对齐,是指数据所在的内存地址必须是该数据长度的整数倍,DWORD数据的内存起始地址能被4除尽,WORD数据的内存起始地址能被2除尽,x86 CPU能直接访问对齐的数据,当他试图访问一个未对齐的数据时,会在内部进行一系列的调整,这些调整对于程序来说是透明的,但是会降低运行速度,所以编译器在编译程序时会尽量保证数据对齐。同样一段代码,我们来看看用VC、Dev-C++和lcc三个不同编译器编译出来的程序的执行结果:

    #include

    int main()

    {

    int a;

    char b;

    int c;

    printf("0x%08x/n",&a);

    printf("0x%08x/n",&b);

    printf("0x%08x/n",&c);

    return 0;

    }

    这是用VC编译后的执行结果:

    0x0012ff7c

    0x0012ff7b

    0x0012ff80

    变量在内存中的顺序:b(1字节)-a(4字节)-c(4字节)。

    这是用Dev-C++编译后的执行结果:

    0x0022ff7c

    0x0022ff7b

    0x0022ff74

    变量在内存中的顺序:c(4字节)-中间相隔3字节-b(占1字节)-a(4字节)。

    这是用lcc编译后的执行结果:

    0x0012ff6c

    0x0012ff6b

    0x0012ff64

    变量在内存中的顺序:同上。

    三个编译器都做到了数据对齐,但是后两个编译器显然没VC“聪明”,让一个char占了4字节,浪费内存哦。

    基础知识:

    堆栈是一种简单的数据结构,是一种只允许在其一端进行插入或删除的线性表。允许插入或删除操作的一端称为栈顶,另一端称为栈底,对堆栈的插入和删除操作被称为入栈和出栈。有一组CPU指令可以实现对进程的内存实现堆栈访问。其中,POP指令实现出栈操作,PUSH指令实现入栈操作。CPU的ESP寄存器存放当前线程的栈顶指针,EBP寄存器中保存当前线程的栈底指针。CPU的EIP寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。

    参考:《Windows下的HEAP溢出及其利用》by: isno

    《windows核心编程》by: Jeffrey Richter

    摘要: 讨论常见的堆性能问题以及如何防范它们。(共 9 页)

    前言

    您是否是动态分配的 C/C++ 对象忠实且幸运的用户?您是否在模块间的往返通信中频繁地使用了“自动化”?您的程序是否因堆分配而运行起来很慢?不仅仅您遇到这样的问题。几乎所有项目迟早都会遇到堆问题。大家都想说,“我的代码真正好,只是堆太慢”。那只是部分正确。更深入理解堆及其用法、以及会发生什么问题,是很有用的。

    什么是堆?

    (如果您已经知道什么是堆,可以跳到“什么是常见的堆性能问题?”部分)

    在程序中,使用堆来动态分配和释放对象。在下列情况下,调用堆操作:

    事先不知道程序所需对象的数量和大小。

    对象太大而不适合堆栈分配程序。

    堆使用了在运行时分配给代码和堆栈的内存之外的部分内存。下图给出了堆分配程序的不同层。

    clip_image001

    GlobalAlloc/GlobalFree:Microsoft Win32 堆调用,这些调用直接与每个进程的默认堆进行对话。

    LocalAlloc/LocalFree:Win32 堆调用(为了与 Microsoft Windows NT 兼容),这些调用直接与每个进程的默认堆进行对话。

    COM 的 IMalloc 分配程序(或 CoTaskMemAlloc / CoTaskMemFree):函数使用每个进程的默认堆。自动化程序使用“组件对象模型 (COM)”的分配程序,而申请的程序使用每个进程堆。

    C/C++ 运行时 (CRT) 分配程序:提供了 malloc() 和 free() 以及 new 和 delete 操作符。如 Microsoft Visual Basic 和 Java 等语言也提供了新的操作符并使用垃圾收集来代替堆。CRT 创建自己的私有堆,驻留在 Win32 堆的顶部。

    Windows NT 中,Win32 堆是 Windows NT 运行时分配程序周围的薄层。所有 API 转发它们的请求给 NTDLL。

    Windows NT 运行时分配程序提供 Windows NT 内的核心堆分配程序。它由具有 128 个大小从 8 到 1,024 字节的空闲列表的前端分配程序组成。后端分配程序使用虚拟内存来保留和提交页。

    在图表的底部是“虚拟内存分配程序”,操作系统使用它来保留和提交页。所有分配程序使用虚拟内存进行数据的存取。

    分配和释放块不就那么简单吗?为何花费这么长时间?

    堆实现的注意事项

    传统上,操作系统和运行时库是与堆的实现共存的。在一个进程的开始,操作系统创建一个默认堆,叫做“进程堆”。如果没有其他堆可使用,则块的分配使用“进程堆”。语言运行时也能在进程内创建单独的堆。(例如,C 运行时创建它自己的堆。)除这些专用的堆外,应用程序或许多已载入的动态链接库 (DLL) 之一可以创建和使用单独的堆。Win32 提供一整套 API 来创建和使用私有堆。有关堆函数(英文)的详尽指导,请参见 MSDN。

    当应用程序或 DLL 创建私有堆时,这些堆存在于进程空间,并且在进程内是可访问的。从给定堆分配的数据将在同一个堆上释放。(不能从一个堆分配而在另一个堆释放。)

    在所有虚拟内存系统中,堆驻留在操作系统的“虚拟内存管理器”的顶部。语言运行时堆也驻留在虚拟内存顶部。某些情况下,这些堆是操作系统堆中的层,而语言运行时堆则通过大块的分配来执行自己的内存管理。不使用操作系统堆,而使用虚拟内存函数更利于堆的分配和块的使用。

    典型的堆实现由前、后端分配程序组成。前端分配程序维持固定大小块的空闲列表。对于一次分配调用,堆尝试从前端列表找到一个自由块。如果失败,堆被迫从后端(保留和提交虚拟内存)分配一个大块来满足请求。通用的实现有每块分配的开销,这将耗费执行周期,也减少了可使用的存储空间。

    Knowledge Base 文章 Q10758,“用 calloc() 和 malloc() 管理内存” (搜索文章编号), 包含了有关这些主题的更多背景知识。另外,有关堆实现和设计的详细讨论也可在下列著作中找到:“Dynamic Storage Allocation: A Survey and Critical Review”,作者 Paul R. Wilson、Mark S. Johnstone、Michael Neely 和 David Boles;“International Workshop on Memory Management”, 作者 Kinross, Scotland, UK, 1995 年 9 月(http://www.cs.utexas.edu/users/oops/papers.html)(英文)。

    Windows NT 的实现(Windows NT 版本 4.0 和更新版本) 使用了 127 个大小从 8 到 1,024 字节的 8 字节对齐块空闲列表和一个“大块”列表。“大块”列表(空闲列表[0]) 保存大于 1,024 字节的块。空闲列表容纳了用双向链表链接在一起的对象。默认情况下,“进程堆”执行收集操作。(收集是将相邻空闲块合并成一个大块的操作。)收集耗费了额外的周期,但减少了堆块的内部碎片。

    单一全局锁保护堆,防止多线程式的使用。(请参见“Server Performance and Scalability Killers”中的第一个注意事项, George Reilly 所著,在 “MSDN Online Web Workshop”上(站点:http://msdn.microsoft.com/workshop/server/iis/tencom.asp(英文)。)单一全局锁本质上是用来保护堆数据结构,防止跨多线程的随机存取。若堆操作太频繁,单一全局锁会对性能有不利的影响。

    什么是常见的堆性能问题?

    以下是您使用堆时会遇到的最常见问题:

    分配操作造成的速度减慢。光分配就耗费很长时间。最可能导致运行速度减慢原因是空闲列表没有块,所以运行时分配程序代码会耗费周期寻找较大的空闲块,或从后端分配程序分配新块。

    释放操作造成的速度减慢。释放操作耗费较多周期,主要是启用了收集操作。收集期间,每个释放操作“查找”它的相邻块,取出它们并构造成较大块,然后再把此较大块插入空闲列表。在查找期间,内存可能会随机碰到,从而导致高速缓存不能命中,性能降低。

    堆竞争造成的速度减慢。当两个或多个线程同时访问数据,而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争。竞争总是导致麻烦;这也是目前多处理器系统遇到的最大问题。当大量使用内存块的应用程序或 DLL 以多线程方式运行(或运行于多处理器系统上)时将导致速度减慢。单一锁定的使用—常用的解决方案—意味着使用堆的所有操作是序列化的。当等待锁定时序列化会引起线程切换上下文。可以想象交叉路口闪烁的红灯处走走停停导致的速度减慢。

    竞争通常会导致线程和进程的上下文切换。上下文切换的开销是很大的,但开销更大的是数据从处理器高速缓存中丢失,以及后来线程复活时的数据重建。

    堆破坏造成的速度减慢。造成堆破坏的原因是应用程序对堆块的不正确使用。通常情形包括释放已释放的堆块或使用已释放的堆块,以及块的越界重写等明显问题。(破坏不在本文讨论范围之内。有关内存重写和泄漏等其他细节,请参见 Microsoft Visual C++(R) 调试文档 。)

    频繁的分配和重分配造成的速度减慢。这是使用脚本语言时非常普遍的现象。如字符串被反复分配,随重分配增长和释放。不要这样做,如果可能,尽量分配大字符串和使用缓冲区。另一种方法就是尽量少用连接操作。

    竞争是在分配和释放操作中导致速度减慢的问题。理想情况下,希望使用没有竞争和快速分配/释放的堆。可惜,现在还没有这样的通用堆,也许将来会有。

    在所有的服务器系统中(如 IIS、MSProxy、DatabaseStacks、网络服务器、 Exchange 和其他), 堆锁定实在是个大瓶颈。处理器数越多,竞争就越会恶化。

    尽量减少堆的使用

    现在您明白使用堆时存在的问题了,难道您不想拥有能解决这些问题的超级魔棒吗?我可希望有。但没有魔法能使堆运行加快—因此不要期望在产品出货之前的最后一星期能够大为改观。如果提前规划堆策略,情况将会大大好转。调整使用堆的方法,减少对堆的操作是提高性能的良方。

    如何减少使用堆操作?通过利用数据结构内的位置可减少堆操作的次数。请考虑下列实例:

    struct ObjectA {

    // objectA 的数据

    }

    struct ObjectB {

    // objectB 的数据

    }

    // 同时使用 objectA 和 objectB

    //

    // 使用指针

    //

    struct ObjectB {

    struct ObjectA * pObjA;

    // objectB 的数据

    }

    //

    // 使用嵌入

    //

    struct ObjectB {

    struct ObjectA pObjA;

    // objectB 的数据

    }

    //

    // 集合 – 在另一对象内使用 objectA 和 objectB

    //

    struct ObjectX {

    struct ObjectA objA;

    struct ObjectB objB;

    }

    避免使用指针关联两个数据结构。如果使用指针关联两个数据结构,前面实例中的对象 A 和 B 将被分别分配和释放。这会增加额外开销—我们要避免这种做法。

    把带指针的子对象嵌入父对象。当对象中有指针时,则意味着对象中有动态元素(百分之八十)和没有引用的新位置。嵌入增加了位置从而减少了进一步分配/释放的需求。这将提高应用程序的性能。

    合并小对象形成大对象(聚合)。聚合减少分配和释放的块的数量。如果有几个开发者,各自开发设计的不同部分,则最终会有许多小对象需要合并。集成的挑战就是要找到正确的聚合边界。

    内联缓冲区能够满足百分之八十的需要(aka 80-20 规则)。个别情况下,需要内存缓冲区来保存字符串/二进制数据,但事先不知道总字节数。估计并内联一个大小能满足百分之八十需要的缓冲区。对剩余的百分之二十,可以分配一个新的缓冲区和指向这个缓冲区的指针。这样,就减少分配和释放调用并增加数据的位置空间,从根本上提高代码的性能。

    在块中分配对象(块化)。块化是以组的方式一次分配多个对象的方法。如果对列表的项连续跟踪,例如对一个 {名称,值} 对的列表,有两种选择:选择一是为每一个“名称-值”对分配一个节点;选择二是分配一个能容纳(如五个)“名称-值”对的结构。例如,一般情况下,如果存储四对,就可减少节点的数量,如果需要额外的空间数量,则使用附加的链表指针。

    块化是友好的处理器高速缓存,特别是对于 L1-高速缓存,因为它提供了增加的位置 —不用说对于块分配,很多数据块会在同一个虚拟页中。

    正确使用 _amblksiz。C 运行时 (CRT) 有它的自定义前端分配程序,该分配程序从后端(Win32 堆)分配大小为 _amblksiz 的块。将 _amblksiz 设置为较高的值能潜在地减少对后端的调用次数。这只对广泛使用 CRT 的程序适用。

    使用上述技术将获得的好处会因对象类型、大小及工作量而有所不同。但总能在性能和可升缩性方面有所收获。另一方面,代码会有点特殊,但如果经过深思熟虑,代码还是很容易管理的。

    其他提高性能的技术

    下面是一些提高速度的技术:

    使用 Windows NT5 堆

    由于几个同事的努力和辛勤工作,1998 年初 Microsoft Windows(R) 2000 中有了几个重大改进:

    改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构,防止多线程式的使用。但不幸的是,在高通信量的情况下,堆仍受困于全局锁,导致高竞争和低性能。Windows 2000 中,锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。

    使用 “Lookaside”列表。堆数据结构对块的所有空闲项使用了大小在 8 到 1,024 字节(以 8-字节递增)的快速高速缓存。快速高速缓存最初保护在全局锁内。现在,使用 lookaside 列表来访问这些快速高速缓存空闲列表。这些列表不要求锁定,而是使用 64 位的互锁操作,因此提高了性能。

    内部数据结构算法也得到改进。

    这些改进避免了对分配高速缓存的需求,但不排除其他的优化。使用 Windows NT5 堆评估您的代码;它对小于 1,024 字节 (1 KB) 的块(来自前端分配程序的块)是最佳的。GlobalAlloc() 和 LocalAlloc() 建立在同一堆上,是存取每个进程堆的通用机制。如果希望获得高的局部性能,则使用 Heap(R) API 来存取每个进程堆,或为分配操作创建自己的堆。如果需要对大块操作,也可以直接使用 VirtualAlloc() / VirtualFree() 操作。

    上述改进已在 Windows 2000 beta 2 和 Windows NT 4.0 SP4 中使用。改进后,堆锁的竞争率显著降低。这使所有 Win32 堆的直接用户受益。CRT 堆建立于 Win32 堆的顶部,但它使用自己的小块堆,因而不能从 Windows NT 改进中受益。(Visual C++ 版本 6.0 也有改进的堆分配程序。)

    使用分配高速缓存

    分配高速缓存允许高速缓存分配的块,以便将来重用。这能够减少对进程堆(或全局堆)的分配/释放调用的次数,也允许最大限度的重用曾经分配的块。另外,分配高速缓存允许收集统计信息,以便较好地理解对象在较高层次上的使用。

    典型地,自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设计成一套固定大小(如 32 字节、64 字节、128 字节等)。这一个很好的策略,但这种自定义堆分配程序丢失与分配和释放的对象相关的“语义信息”。

    与自定义堆分配程序相反,“分配高速缓存”作为每类分配高速缓存来实现。除能够提供自定义堆分配程序的所有好处之外,它们还能够保留大量语义信息。每个分配高速缓存处理程序与一个目标二进制对象关联。它能够使用一套参数进行初始化,这些参数表示并发级别、对象大小和保持在空闲列表中的元素的数量等。分配高速缓存处理程序对象维持自己的私有空闲实体池(不超过指定的阀值)并使用私有保护锁。合在一起,分配高速缓存和私有锁减少了与主系统堆的通信量,因而提供了增加的并发、最大限度的重用和较高的可伸缩性。

    需要使用清理程序来定期检查所有分配高速缓存处理程序的活动情况并回收未用的资源。如果发现没有活动,将释放分配对象的池,从而提高性能。

    可以审核每个分配/释放活动。第一级信息包括对象、分配和释放调用的总数。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介绍的许多技术之一,这种关系可以用来减少内存分配。

    分配高速缓存也起到了调试助手的作用,帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名,甚至能够找到确切的失败的调用者。

    MP 堆

    MP 堆是对多处理器友好的分布式分配的程序包,在 Win32 SDK(Windows NT 4.0 和更新版本)中可以得到。最初由 JVert 实现,此处堆抽象建立在 Win32 堆程序包的顶部。MP 堆创建多个 Win32 堆,并试图将分配调用分布到不同堆,以减少在所有单一锁上的竞争。

    本程序包是好的步骤 —一种改进的 MP-友好的自定义堆分配程序。但是,它不提供语义信息和缺乏统计功能。通常将 MP 堆作为 SDK 库来使用。如果使用这个 SDK 创建可重用组件,您将大大受益。但是,如果在每个 DLL 中建立这个 SDK 库,将增加工作设置。

    重新思考算法和数据结构

    要在多处理器机器上伸缩,则算法、实现、数据结构和硬件必须动态伸缩。请看最经常分配和释放的数据结构。试问,“我能用不同的数据结构完成此工作吗?”例如,如果在应用程序初始化时加载了只读项的列表,这个列表不必是线性链接的列表。如果是动态分配的数组就非常好。动态分配的数组将减少内存中的堆块和碎片,从而增强性能。

    减少需要的小对象的数量减少堆分配程序的负载。例如,我们在服务器的关键处理路径上使用五个不同的对象,每个对象单独分配和释放。一起高速缓存这些对象,把堆调用从五个减少到一个,显著减少了堆的负载,特别当每秒钟处理 1,000 个以上的请求时。

    如果大量使用“Automation”结构,请考虑从主线代码中删除“Automation BSTR”,或至少避免重复的 BSTR 操作。(BSTR 连接导致过多的重分配和分配/释放操作。)

    摘要

    对所有平台往往都存在堆实现,因此有巨大的开销。每个单独代码都有特定的要求,但设计能采用本文讨论的基本理论来减少堆之间的相互作用。

    评价您的代码中堆的使用。

    改进您的代码,以使用较少的堆调用:分析关键路径和固定数据结构。

    在实现自定义的包装程序之前使用量化堆调用成本的方法。

    如果对性能不满意,请要求 OS 组改进堆。更多这类请求意味着对改进堆的更多关注。

    要求 C 运行时组针对 OS 所提供的堆制作小巧的分配包装程序。随着 OS 堆的改进,C 运行时堆调用的成本将减小。

    操作系统(Windows NT 家族)正在不断改进堆。请随时关注和利用这些改进。

    Murali Krishnan 是 Internet Information Server (IIS) 组的首席软件设计工程师。从 1.0 版本开始他就设计 IIS,并成功发行了 1.0 版本到 4.0 版本。Murali 组织并领导 IIS 性能组三年 (1995-1998), 从一开始就影响 IIS 性能。他拥有威斯康星州 Madison 大学的 M.S.和印度 Anna 大学的 B.S.。工作之外,他喜欢阅读、打排球和家庭烹饪。

    http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835

    我在学习对象的生存方式的时候见到一种是在堆栈(stack)之中,如下

    CObject object;

    还有一种是在堆(heap)中 如下

    CObject* pobject=new CObject();

    请问

    (1)这两种方式有什么区别?

    (2)堆栈与堆有什么区别??

    ---------------------------------------------------------------

    1) about stack, system will allocate memory to the instance of object automatically, and to the

    heap, you must allocate memory to the instance of object with new or malloc manually.

    2) when function ends, system will automatically free the memory area of stack, but to the

    heap, you must free the memory area manually with free or delete, else it will result in memory

    leak.

    3)栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

    4)堆上分配的内存可以有我们自己决定,使用非常灵活。

    整理自:http://blog.csdn.net/xuysh427/archive/2008/11/05/3229300.aspx

    http://blog.csdn.net/Augusdi/archive/2010/07/01/5706327.aspx

     

    展开全文
  • 1 B 端产品经理 如何理解B端产品? B端产品主要分为两大类: 为公司的管理服务,如:HR系统、OA系统; 为公司的运营服务,如:供应链系统、ERP系统的。 B端产品即要符合商业组织的战略要求,能够满足商业用户...

    很幸运的是2019年3月份读完了这本B端产品经理必修课,今天也就是2019年11月25日整理书籍再次拿出来看的时候,自己已经身在小米,主要是我当时忘记这本书的作者就是现在的同事宽同学了,了解其人,更要从书中再去品味。

    产品经理的沟通技巧:沟通、说服、谈判、演讲、辩论。

    ABC理论:假设影响行为,行为最终影响结果。在沟通过程中要以结果为导向,抛弃偏见,以开放的态度与每个人沟通。

    绊倒产品经理的6个绳索:

    1. 太在意过程。要明确目标,以结果为导向。
    2. 胡言乱语。要明确沟通目的。
    3. 不推不动。积极主动是一个好习惯。
    4. 不学习。多读书、看新闻、参加沙龙,不要停止好奇与学习。
    5. 焦头烂额:要学会时间管理,分清优先级。

    产品经理:为创造价值而生。案例:《未来的传奇——波音747的故事》

     

    第一部分 To B or not to B

    1 B 端产品经理  

    如何理解B端产品?

    B端产品主要分为两大类:

    • 为公司的管理服务,如:HR系统、OA系统;
    • 为公司的运营服务,如:供应链系统、ERP系统的。

    B端产品即要符合商业组织的战略要求,能够满足商业用户需求,将已有商业运行逻辑进行系统化、信息化、高效化处理。两类都是为企业流程效率服务,让分散的、低效的个体,更好地连接合作,发挥集成化的、系统化的更大作用。

    相较于C端产品,B端产品最大的特点是:面向特定领域用户,且数量少得多,但更注重对用户专业领域操作流程的深度挖掘——也就是专业性更强,与业务的结合更紧密。

    2  B 端产品经理的职业生涯

    B端产品经理工作:

    B端产品经理技能树:

    B端产品经理职业生涯:产品专员/产品助理>产品经理>高级产品经理>产品总监

    • 1.产品专员/产品助理:关注具体执行层面的协作,对产品的需求细化,以及对原型的设计和文档的整理
    • 2.产品经理:主要关注推动产品迭代、产品的实现与效果、数据和业务、感知业务和产品的发展方向。
    • 3.高级产品经理:主要关注商业价值和模式,以及和产品的全生命周期思考问题。
    • 4.产品总监:主要关注战略规划、业务发展以及团队管理。

    在这条职业发展路径的每个阶段关注的重点不同,要掌握的技能虽多,但不是每一种都需精通,可借鉴“二八原则”:真正重要的知识,或者在实践中被反复使用的知识,只占全部知识的20%。也就是说,20%的知识是需要反复修炼形成骨架的,剩下的80%在此基础上不断更新迭代。所以产品人要一直学习在路上。

    3  以精益思想为产品方法 

    花更少的人,更少的设备,更少的时间和空间为客户提供真正想要的东西。

    • 理念一:快。快时成本与效率的解决之道。
    • 理念二:流动产生价值。长时间没开发的需求就慢慢变得不实用要定期回顾他的价值或重新设计。
    • 理念三:采用最简单的方案。面对各有优劣方案举棋不定面对复杂流程而苦恼时,选取简单的方案是最优选择,结构简单的系统往往是最可靠的。
    • 理念四:处在联系中的事物才能被简化。简化不是减少,将需要简化的部分在系统中进行了转移。
    • 理念五:不害人的需求不是完整的需求。无论多坏的改变,都会有一些人收益;无论多好的改变,都会使一些人受损。在设计规则时,要多角度的考虑获益或者受损的角色。
    • 理念六:化散乱为规律,化应急为预测。懂得预测需求,否则就会疲于奔命。
    • 理念七:只可图示,不可言传。能用图表示的会更直观,更有助于发现问题。
    • 理念八:让公路排满车,就是堵车。将工作焦点转移到重要不紧急的事情上去。
    • 理念九:聚焦目标才能带来明确的结果。做产品如果想讨好所有的用户就会分散目标,变得平庸。
    • 理念十:持续改进,不忘初心。做出的产品方案需要不断优化,同时不断回顾最初目标防止跑偏。
    • 理念十一:细节体现专业。对事物的不断细分才能体现专业性。
    • 理念十二:不要造永动机。思考产品要从整体思考,不要陷入细节。
    • 理念十三:先准确,后精确。探索需求,先力求需求准确,再在此基础上精确的探索需求。

    第二部分 单个产品管理流程

    B端产品经理的工作流程归纳为五个阶段:

    • 产品规划→产品设计→产品研发→数据监控

    1. 规划阶段:基于组织的目标和战略,获取并分析需求,规划B端产品的发展方向和路径

    我们要从规划阶段开始设计我们的B端产品,在规划阶段,我们要开展市场调研、用户调研、产品路线规划、需求分析、需求管理等活动,这些活动分布在《用户体验要素》的战略层和范围层,即主要关注目标和实现目标的边界。

    这一阶段主要是产品经理要考虑的,作为刚入门的产品小白来说,可以先从了解行业动态开始,通过“人人都是产品经理”网站、喜马拉雅“36氪”等各种途径来了解,可以在茶余饭后与同事朋友聊聊,开拓思路。

    2. 设计阶段:基于需求和规划,设计产品信息架构、原型、交互、UI方案等

    在设计阶段,我们要开展设计信息架构,设计产品原型、设计交互、设计UI等活动。这些活动分布在《用户体验要素》的结构层、框架层和表现层,即要在界定的边界内勾划出最终输出物大体轮廓和具体执行方案及最终的输出物—产品。

    这一阶段涉及到具体执行层面,也是产品专员或产品助理应该重点关注的环节。目前阶段产品经理已经通过规划和分析需求了解到用户想做什么了,这一阶段即让概念进入产品化阶段。先不要急打开axure,我们需要先梳理出业务流程图和信息架构图,在此基础上再去进行细化,为了防止我们画原型时缺页面可以先梳理出页面流程图,最后再一气呵成完成你的原型设计。

    3. 研发阶段:根据已经设计好的产品方案,设计技术实现方案及推动产品研发。

    我们完成了设计阶段的工作后,将进入到研发阶段。在研发阶段,产品经理要协助研发开展产品开发工作。这个活动分布在《用户体验要素》的表现层,即关注最终的产出物—产品。虽说产品经理不需要写代码,但要承担项目管理、协助研发理清需求、协助测试开展测试以推进产品开发。

    在这个过程中,需要随时随地解答技术人员对需求的疑问以及协助测试人员将优化和bug分类整理,并安排优先级进行分批处理。

    4. 发布阶段:制订产品发布前的部署和培训计划,推动产品上线。

    B端产品在完成研发后,将进入发布阶段。在发布阶段,产品经理要开展制定产品发布方案、发布产品的活动。这些活动分布在《用户体验要素》的框架层和表现层。即关注具体的业务流程和最终的产品。

    在这个阶段前,要确认以下信息做好充分准备:

    • 1.产品是否具备待上线条件,比如是否有测试报告,是否得到使用方的验收通过;
    • 2.产品的操作手册和培训安排是否完成;
    • 3.产品上线时间是否合适,确保不要影响其他业务的操作。其他细节这里不赘述。

    5. 监控阶段:监控产品上线后的效果,收集并分析用户反馈的信息,并形成新的需求。

    在发布B端产品之后,产品经理将进入监控阶段。在监控阶段,产品经理要开展制定关键指标、收集及分析反馈信息的活动。这些活动分布在《产品体验要素》的框架层和表现层,即主要关注具体的业务流程和最终的产品。在监控阶段,产品经理要使用数据来监控产品上线后的效果,以及收集用户的反馈意见,最终为开启新的单个产品管理流程做准备。

    上线一段时间后,需要产品经理写上线邮件,主要目的有三个:

    • 1.总结与记录:总结项目过程,未来翻查资料速度超快;
    • 2.项目推动:产品上线后才是开始,需要推动、协调各方资源;
    • 3.团队润滑剂:给参与者帮助你的人正面反馈。监控阶段手机的新需求和反馈进行整理分析,用于后期优化产品。

    总体来说,B端产品经理主要关注3个方面:表现层、领域层、数据层。

    • 表现层:即用户界面,用户直接与系统进行交互和操作;
    • 领域层:是商业和业务逻辑,是核心关注点;
    • 数据层:关注是系统之间的交互与数据存储,系统之间会以接口的形式传送数据,关注接口传输性能、传输内容等。

    4 规划阶段:产品设计的开始 

    一、产品规划:调研市场→调研用户→规划产品路线→分析需求→管理需求

    在规划行动方案之前,一定要记得先问自己:有什么事情我“今天”做了,可以让“明天”更好,或者至少让“明天”不会更糟。

    1.调研市场:找B端竞品。

    目的:分析产品可能存在的盈利点,获取行业经验和方向

    需要:产品创意、行业信息

    方法:商业模式画布、SWOT分析、竞品分析

    指标:竞品分析报告、商业需求文档

    • 明确目的。想清楚你要查询的信息,定好方向再起步。
    • 与业务同事沟通。咨询业务同学竞品名字。
    • 了解专有名词。如ERP、WMS,通过搜索专有名词找到可用资料。
    • 找到同类SaaS产品。
    • 搜索信息渠道。知乎、简书,知网、万网。

    2.调研用户:倾听用户声音。

    目的:分析和研究产品使用者

    需要:竞品分析报告、商业需求文档、产品创意

    方法:用户研究方法(问卷调研、用户访谈等)

    指标:用户调研报告

    • 用户的话不能全信。为了引起重视而故意夸大,害羞或怕说错话不去表达真实想法。
    • 能有的功能,用户都希望有。人性贪婪。
    • 明确词语含义。“我希望报表更快一点”“更快一点”就需要进一步明确。
    • 尽量不要问有固定选项的问题。列选项即使没有他也会选择。不认让用户给选项打分0-10分。
    • 重述用户所说。将用户的话用产品经理的语言再说一遍,让用户判断说的对不对。
    • 别让用户预测。不要让用户设计产品,与未来相比用户当下的行为更有准确性。

    师徒制,三段式问法:请教>刨根问底>核实

    1)发现问题:你正在做什么事情?做的过程中有什么不舒服的吗?遇到了什么问题?

    2)分析流程:你现在用什么方法来解决整个问题?

    3)探索机会:为了更好的解决整个问题,你认为有什么方法可以帮到你?或者哪些地方可以优化下?

    3.规划产品路线:缩小现在与未来的差距  

    目的:规划产品路线、节奏

    需要:竞品分析报告、商业需求文档、产品创意、用户调研报告

    方法:

    • ①列出为了缩小差距所要做的事情
    • ②目前产品的约束条件,找出其中能做到的事情
    • ③预测这些事情会使产品有怎么样的结果
    • ④给这些结果排序,给他们加上一个期望日期

    指标:产品发展路线图Roadmap(实现时间、名称、目标、功能、优先级、度量标准)

    • 时间:完成时间是什么时候
    • 名称:实现的产品名称和版本号是什么
    • 目标:要实现什么样的目标,以及想要获得的收益
    • 功能:实现的功能是什么优先级这些功能的优先级是什么指标用什么标准来衡量已经完成并实现的计划

    有产品目标后要做好目标管理,规划行动方案,实时反馈并验收成果。

    1、分析和预测需求。

    产品经理首先要明确与产品成败相关的因素。要了解用户对各因素的期望。之后用现在和未来的时间维度去分析获得信息。从现在和未来的角度发现差异,目前用户从我们产品获得什么?是否让其满意?接下来用户还希望产品有哪些功能?

    2、现状分析。

    分析目前自己的产品处于什么状态。目前该产品与行业优秀产品有什么区别?

    3、缩小差距。

    • a、用头脑风暴列出为缩小差距所要做的事情。
    • b、思考从目前的约束条件列出清单中可以做的事情。
    • c、已经选出的事情会使产品有怎样的结果,最好能够测量。
    • d、给结果排序,列出优先级及期望实现日期。

    4.分析需求:用图形代言需求   

    目的:将需求具体化

    需要:竞品分析报告、商业需求文档、产品创意、用户调研报告

    方法:筛选需求(需求蛋模型)→思考需求(D×V×F>R)→解析需求(UML统一建模语言)

    指标:需求说明文档

    需求应有的特征:

    • 痛点:好的需求犹如根治用户痛处的良药。B端产品通过调研用户基本可提炼出痛点。
    • 收益:需求应有可量化的结果导向。
    • 明确、可行、简单的第一步:挖掘需求就是降低需求中的含混性,使之明确。如果在需求落地成型阶段才发现含混性,这个时候的改正成本实在是太高了。

    需求的变革公式:不满情绪*变革愿景*初步实践>变革阻力。对现状的不满、对变革的期盼、愿意迈出明确的第一步等其中任何一个因素没做到将导致变革失败。

    • 需求的可行性=(需求的当前价值+未来价值)/(需求的实现成本+维护成本)
    • 解析需求:数据驱动,行为产生数据,数据联系行为。数据流动形成数据流,从而把业务中的人联系在一起。

    举例设计一个咖啡馆的管理系统

    • 1、画流程图先把主要流程总结出来。

    进店——点餐——下单——制作食物——送餐——就餐——结账——离店

    • 2、对主要流程进行细化。如果流程图中的活动数量超过7+-2的范围,则颗粒度太细或太粗。

    比如将点餐流程进行细化

    • 3、实体关系图(ER图)

    数据之间三种对应关系:一对一、一对多、多对多

    • 一对一:顾客就餐完成后需要支付自己的账单。顾客1——1账单
    • 一对多:服务员工作可以为多个顾客服务。服务员1——n顾客
    • 多对多:面包、咖啡等可以被不同客人点单。菜品n——n顾客

    数据对象的属性也是一类数据,用来描述数据对象,并且多个数据对象可以包含相同的属性。如何区分数据对象和属性:xx单、xx表一般都是数据对象,数据对象区别于其他实物独立存在的个体,数据对象一般能用量词“类”来形容。数据对象的属性数据可被用来增删查改,可以通过该来查漏补缺。

    • 4、数据流程图

    数据:表示数据流,连接数据流程图的各元素。

    外部实体:外部实体表示系统之外的人或事物,它可以成为整个数据流的起点或者终点。

    数据储存:存储数据的区域。在现实中,可能是单或者表格表格。

    活动操作:对数据进行操作,包括数据的流入和流出。

    • 5、用例图

    用例是对产品功能需求的描述。

    需求文档

    • 1、需求名称
    • 2、背景
    • 3、目标与收益
    • 4、功能需求。业务概念、流程展示、需求描述。
    • 5、非功能需求

    5.管理需求:打造简单可实践的需求池 

    目的:将需求具体化

    需要:产品发展路线图、需求说明文档、产品创意

    方法:需求收集(急诊模式)→需求设计(登机模式)→需求研发(看板模式)

    指标:需求池、需求排期计划

     

    需求的重要性:为了区分同一优先级的多个需求,可以用重要性来辅助优先级管理需求。

    重要性就是对需求进行打分,分数范围是1—100分(根据5个优先级可以分成5等分),每个需求的分数是唯一的。优先做分数大的需求。

    优先级和重要性一旦确定,所有的资源将向这些需求倾斜。处理跨部门的需求时,使用优先级尤为重要,但重要性的分数不能跨部门比较。

    5 设计阶段:产品从概念到解决方案

    产品设计:设计产品架构→设计产品原型→设计交互→设计 UI

    1.设计产品架构:设计让产品立得住的骨架 

    需要:产品发展路线图、需求说明文档、需求排期计划

    方法:   设计信息架构(三要素:情景、内容、用户)→输出站点地图(UML)

    指标: 站点地图

    信息架构(收纳信息)

    • 信息架构三要素:情景,内容、用户。
    • 信息架构五组件:组织系统、标签系统、导航系统、搜索系统,
    • 组织信息:根据时间字母等对信息进行组织分类。
    • 给信息加标签:用一个名称对大量的信息进行概括,就是给信息加入了标签,便于快速查询。
    • 设置找到信息的路径:导航
    • 搜索信息:搜索功能
    • 描述信息的特征:通过各种条件筛选数据。

    站点地图(原型设计起点)

    • 各页面的层级关系。b端产品的四种基本页面类型:表单页、详情页、列表页、Dashboard页
    • 表单页:用户向系统增加、删除、提交信息的操作页面。
    • 详情页:展示详细信息。
    • 列表页:向用户展示结构化的数据信息。列表页的设计大部分来自用户对实际数据的操作和展示。
    • Dashboard页:仪表盘,监控系统运营情况。

    2.设计产品原型:高效产出原型的方法

    需要:站点地图、需求说明文档

    方法:   交互设计、排版、axure 技能、

    指标: 产品原型、PRD 文档

    模式思维

    模式,指可以重复使用的方式和方法。类似于乐高积木原理。

    以厨房设计为例,厨房空间需要炉灶、水槽、食物储存区、操作台四个区域。以上四个部分距离不能太大在3m以内,操作台的范围大致在1.2-3.6m。要在一个页面上满足用户多种活动需求,比如信息查看、搜索、下载等,每种活动对应一种解决方案,这个解决方案就是模式。设计模式是由组件组成,组件是构成设计模式的基本元素。

    因此设计产品原型的流程:

    • 1、根据站点地图,找到要设计的页面类型。(列表页、表单页、详情页、Dashbard页等)
    • 2、根据页面类型对应用户操作行为,思考出各自对应的模式。
    • 3、用组件搭建成对应的模式。各种模式的布局和组合最终形成产品原型。

    总结属于自己的设计模式

    • 1、模式名称:给自己的模式起个名称,便于管理交流。比如,搜索单据。
    • 2、概念和价值:描述清楚这个模式是什么,即给模式下一个定义。写清楚给用户带来什么价值。
    • 3、使用范围:该模式相关的边界条件。比如在用户登录情况下,向用户推荐常用信息。
    • 4、模式描述:用文字图片等形式描述清楚模式由哪些组件构成及该模式是如何运行的。如:用户在输入框录入关键词时,会实时展示提示信息,便于用户选择。
    • 5、相关模式:与这个模式相关的模式还有哪些。

    三种精度的产品原型展示

    • 低精度原型:即页面流程图,展示页面中的关键组件及页面之间的跳转流程。
    • 中精度产品原型:像照片一样,展示包含所有组件的页面,主要展现页面布局。
    • 高精度产品原型:详细展示原型中各个组件在不同操作下所展示的信息。

    需求文档加上 网站地图及产品原型就为产品需求文档。

    3.设计交互:让B 端产品简单易用  

    B端产品更加偏重于工具属性,注重帮助用户完成工作效率和效果。所以,设计C端产品的交互更像是设计一本赏心悦目的小说,设计B端产品更像是一本产品说明书,需要追求使用的高效和易学性。

    4. 设计UI:如何与设计师高效沟通 

    跟设计师的合作注意以下几点:

    1. 主动学习设计知识,如:常逛逛Dribble、优设、站酷之类的设计网站,提高自己对设计的认知。同时,了解公司或团队的设计规范。
    2. 明确指出设计重点,表达顺序。明确页面中重点功能是什么,使用者在什么场景下使用,以及希望用户重点使用的界面组件和信息有哪些。
    3. 给出设计案例。可以找一些比较好的设计案例给设计师参考,指出案例中哪些元素可以参考。

    尼尔森十大可用性原则

    • 系统状态可见:用户能够随时获得产品反馈的信息,会让用户产生对产品的信任和安全感。
    • 系统与真实世界匹配:要参考真实环境使用的单据和报表,将其映射在产品中。
    • 用户掌控和自由操作:用户可以自由退防护或者结束当前任务。
    • 一致性和标准化:让界面元素和操作形成一套让用户可识别、可学习的标准,并且在产品的任何地方都可以应用。
    • 避免错误:需要检查一下界面的按钮是否可能产生误触。
    • 直接识别比记忆好:产品要减少用户的记忆负担。
    • 灵活高效地使用:要不断地提高界面使用效率
    • 美观和简约的设计:设计要简明突出。
    • 帮助用户识别、诊断和解决错误:着重关注给用户反馈的操作信息,且尽可能以友善的态度表达。
    • 帮助和文档:需要在界面上提供必要的使用帮助,并整理出专门的产品使用文档帮助用户学习。

    6 研发阶段:产品方案的实现

    产品研发:项目启动→规划→执行→监控→收尾

    1.项目启动

    说明项目目标、阶段划分、组织结构、管理流程等关键事项

    2.规划

    明确研发工作内容以及各需求点的研发、测试负责人,评估研发时间,制定排期计划表

    c74f78d09f414bb4c6fdc280e845e405-picture

    3.执行  (一个Java项目的标准开发流程

    总体设计→概要设计→详细设计→编写代码→代码审核→单元测试→集成测试→系统测试→发版上线

    4.监控

    对项目输出成果或者阶段性成果进行检查,看看是不是我们想要的或是缺少了什么

    PS:需求看板可以有效管理各需求进度,防止需求堆积拥堵导致项目不能按时交付

    deabb9c4f7e461fec6e0088f7e51a8b6-picture

    5.收尾

    试用、培训、维护、项目回顾复盘

    项目管理

    在研发阶段,产品经理需要承担起项目管理的义务,协助研发和测试同事,以推进产品开发。

    项目管理的四个维度:范围、时间、质量、成本。

    可对应的项目目标:多、快、好、省。

    1、核心问题,什么是项目?项目是为创造独特的产品、服务或者成果而进行的临时性工作。据此对项目有三个定义。

    • 项目有明确的开始和结束,也就是项目有明确的开始时间和结束时间。没有明确开始时间和结束时间的活动称之为运营。运营是一个通过连续不断的工作来交付成果。
    • 项目会产生成果。最终提供用户使用的产品功能。
    • 项目计划随着项目的开展而逐渐详细。项目会随着计划的开展展现很多之前未考虑到的细节

    2、项目目标,多、快、好、省在将要延期的情况下可以考虑砍掉部分功能而不要增加开发资源。

    3、项目计划:

    项目风险管理:

    项目风险:如果发生不确认的条件和时间,会对一个或多个项目目标造成影响。

    项目沟通:

    原则:不论采用何种手段,邮件、微信、电话、面谈,信息的发出方一定要保证接收方能够收到并且理解信息,做出反馈。

    项目推进:

    推进项目的重要基石是:标准化——标准化指完成某项工作的最佳工作方法。

    产品经理可以将项目过程遇到的问题及处理方法、人员配合方式、项目流程等经验或文档分享给其他项目成员,推而广之,达成大家的共识。

    比如:

    • 项目会议纪要模板:帮助大家高效输出内容完备的会议纪要。
    • 上线验收清单模板:让大家按照清单和步骤执行可以减少出错、提高效率。
    • 项目工作流:明确各自角色的任务及配合时间点,团队配合更紧密。

    标准化可以避免项目再次陷入相同的错误中。沿用成功的工作方法、经验,让项目不断被顺利推进。

    而在研发日常跟进中,可以采用看板模式来记录和跟进。看板管理需要注意的就是:避免某个阶段的需求出现拥堵,或者是一旦发现拥堵,要及时疏解。

     需求卡片可以包含如下信息(工具Trello、Teambition)

    • 1、需求名称
    • 2、需求的相关人:需求人、负责人、产品经理、研发工程师
    • 3、需求类型:如需求涉及哪些系统、哪些部门等
    • 4、需求完成时间
    • 5、需求描述:可以附上产品文档
    • 6、需求优先级

    7 发布阶段:产品上线的临门一脚

    上线前需确认的信息:

    1. 产品是否具备上线条件,比如:是否有测试报告,是否得到使用方的验收。
    2. 产品的操作培训是否完成,或者是否至少有使用说明文档。
    3. 产品上线时间是否合适。产品上线的时间点是否会影响其他业务操作,是否需要配合整体的运营计划。

    产品发布:

    产品推广产品,可运用营销推广模型的核心思路:描述一个重要的问题,并让大家认同,之后介绍产品给出的解决方案。

    营销推广模型分7步:

    1. 背景介绍:介绍所发布产品的背景信息,比如:时间、地点、任务、事件等信息,便于大家了解背景知识,从而减少认知负担。
    2. 描述阻碍:描述用户目前会遇到的问题,并让大家认同该问题确实会给自己带来不便。
    3. 点燃希望:向大家说明这个问题有解决方案,引起打击的期待和注意。产品经理客户可以介绍这个问题的解决方案,及概念或者同行业对这个问题的解决思路。
    4. 震撼登场:抛出问题的解决方案——即发布的产品是什么。
    5. 展现价值:描述这样的解决方案和产品会给用户带来怎样的价值和收益,可以配数字,这样会更有说服力。
    6. 精雕细琢:介绍产品重要的细节、工作原理。
    7. 给出诱惑:给大家送一些福利,让大家快来体验产品。这里可以根据实际情况来选择使用。

    发布一款产品或者介绍一个功能并不都需要发布会的形式,产品经理可以应用简单有效的演讲框架快速打动用户。

    8 监控阶段:让产品不断生长  

    8.1 制订数据指标及目标:产品演进的航标  

    • 8.1.1 数据指标的黑箱和二律背反  
    • 8.1.2 关键成功因素法:制订数据目标的方法  

    8.2 收集及分析反馈信息:整装待发  

    • 8.2.1 零基础快速入门SQL 的方法  
    • 8.2.2 与用户座谈的产品回顾会  

    数据监控:制定关键指标→收集分析反馈信息

    1.制定关键指标

    方法:关键成功因素法,输出OGSM表(如图)

    定位长期目标→制定对应的短期目标→找到实现短期目标的关键成功因素(CSFs)→确定 CSFs 实施的测量方法

    5fcd58bd96f7eb1cdf86b13847f05510-picture

    2.收集及分析反馈信息

    产品回顾会:制定会议章程(会议邀请邮件)→展现事实(影响/问题)→集思广益(问题&方案)→决定做什么(总结行动项、负责人、deadline)→总结和公告(整理会议纪要)

    产品监控数据指标:

    数据监控应该监控什么才有意义,从黑箱、仪表盘和二律背反理论中我们可以得到一些启发。

    • 黑箱:

    我们只能输入和输出,而并不知道事物真正运行的原理是什么,比如:电商网站的输入是用户进站浏览,输出是订单。那用户在浏览网页所做的行为和决策就是黑箱。 通过研究黑箱,我们可以提升用户转化率。

    • 仪表盘:

    通过汽车的仪表盘速度、耗油量等数据指标,随时反馈出汽车的状态。没有仪表盘的汽车随时都有失控的危险。对系统运行状态的监控也是,数据指标要尽可能覆盖全面,比如:出现问题的次数、加载时间、业务相关数据等。

    • 二律背反:

    二律背反指规律中的矛盾,在互相联系的两种力量的运动规律之间存在的相互排斥现象——即两种事物此消彼长、此长彼消、相背相反。

    因此,我们除了关注数据指标之间的相关性,更需要找到这些处在二律背反的指标,然后进行指标配对。通过指标配对,防止过度监控或者提升一个指标而带来副作用,用另一个指标来辅助分析和监控,从而权衡出好的办法以解决问题。

    德鲁克说:如果没办法计量就没办法管理。数据指标就是管理量化的表现。监控部分数据指标来监控系统的运行状态,如B端产品数据指标包括出现问题的次数、加载时间、以及业务相关数据指标。

    制定数据目标思路:关键成功因素法

    • 定位长期目标。产品经理找到组织或者团队的长期目标是节省成本。
    • 为了实现长期目标,需要制定对应的短期目标。如在长期目标的基础上拆解出短期要完成的目标是减少包装成本。
    • 找到实现短期目标的关键成功因素。如实现减少包装成本的短期目标可以做的工作是系统推荐使用的包装盒形状等。
    • 确定关键成功因素实施的测量方法。找到所要实现目标所要做的事情后,需要一个标准来测量是否施行到位。如,我们使用推荐准确率达到90%的指标来监测。

    数据采集:

    SQL是查数据和做报表的工具,建议产品经理都要学:

    • SQL可能是最容易入门的编程语言。因为他书写出来的代码,完全是按照英语语法,是初中语法中最简单的部分。只要学习非常少的SQL知识,或者说是几个英语单词,就可以快速在工作中使用。
    • 使用频率非常高。
    • 有助于产品经理理解数据分析的思路。

    SQL 入门手段:

    • 《SQL基础教程》,这本书内容实用且基础,适合零基础的人学习,且它描绘了很多使用场景。
    • 学习编程的网站,如http://www.w3school.cn/ ,这里的教学内容简约便捷,可以当成SQL使用的工具字典
    • 找一名程序员同事当老师,随时实践、随时请教问题。

    在监控指标时需要注意的细节:

    制定数据目标的原则:具体、可衡量、可实现、有相关性、有截止时间。

    根据关键成功因素的分析思路最终输出OGSM表。OGSM是Obiective(长期目标)、Goal(短期目标)、Strategy(策略)、Measurement(测量方法)名词的首字母。形成另一种展现形式长期目标:节省成本短期目标策略/关键成功因素测量方案行动方案减少包装成本系统推荐包装盒形状系统推荐率达到90%的指标Q1完成功能研发产品回顾会

    • 制定会议章程。会议前产品经理发送邮件包括开会目标、会议议题、时间地点、会议流程、参与人员、准备资料等。产品经理要保障会议内容简单明了,以及重要人员可以出席。会议开始后产品经理也要重申会议章程内容,让与会者明确会议议题。
    • 展现事实。阐述与产品相关的实际情况,上线后对业务运营数据的影响,或产品出现的问题和故障有哪些?记录会议内容,控制好节奏。
    • 集思广益。陈述完事实后,讨论找到解决方案。
    • 决定做什么。讨论完成后产品总结会议后的行动项,以及行动的负责人和完成时间,便于会议内容的追踪和落实。
    • 总结和公告。会议收尾时,产品经理作为主持人要总结本次会议所有参会人都同意的重要结论。散会后将结论总结为会议纪要发给相关人来备忘。

    第三部分 产品经理的自我管理

    1、帕金斯定律人在做一件事情时,耗费的时间越长就会感到越累。

    2、产出=活动*杠杆率越符合组织、团队战略或目标的活动越具有高杠杆率。

    3、提升工作速度

    • 建立收件箱:产品经理不要被当前来的事情打断,让当前来的事情进入收件箱,然后分配时间去处理。
    • 把场景相同的活动放在一起做:把类似的事情集中在一起做,减少任务切换的时间。
    • 找到关键路径:产品经理在必须做的事情中可以插入可以并行的工作。
    • 制定每天的活动计划:每天开始要预想一天的工作,哪些是重点工作,哪些是可以放到一起做的工作。但也不要把每天的工作排的非常满,导致没有时间应对紧急情况。
    • 合理拒绝别人的猴子:对于做不了或者暂时没时间做的工作坚决说不。如果在接受事情前,没有评估好是否能保质完成,那就违背产品经理的职责。
    • 学会番茄工作法:没工作一段时间就休息一会。

    不主动,工作没有重点,求大求全是产品经理的绊脚石。

    4、产品力

    产品经理的技能可以分为硬技能和软技能。

    • 硬技能包括:用户调研、产品规划、需求分析与管理、产品方案设计、数据分析等。
    • 软技能包括:项目管理能力、时间管理能力、沟通能力等。

    5. 产品力的获得途径

    作者的个人情况或习惯:

    • 喜欢看书、涉猎历史、哲学、科学、经管、互联网、技术等各个领域的书籍。
    • 把写书列为一个长期的目标,规划了5年左右的时间。
    • 因为B端产品经理的知识没有成型理论和体系,所以,作者立志填补这项空白。期望自己的总结和思考,为中国产品经理职业发展提供理论和实践的支持。
    • 为了写书,查阅大量现有的互联网、经管类书籍,还有大量的软件工程类书籍,以及学术论文。
    • 查阅资料注重追溯知识本源,了解知识的核心要义。
    • 本书展示了作者对B端产品经理的理解,介绍了B端产品经理的工作流程、工作方法、工作场景,以及作者在工作中的经验总结。
    • 喜欢跟研发同事散步聊产品和设计,在无拘无束的畅谈中总结自己对产品的看法和观点。

    以上,可以了解到作者身上几个难能可贵的品质:

    • 对世界充满好奇。在好奇心的驱动下,习得的知识非常宽广。
    • 对产品工作发自肺腑的热爱。兴趣让学习、规划和实践更加纵深。
    • 目标驱动、规划落地。目标明确,为达目标时刻准备。
    • 喜欢追根溯源。了解知识时追求本源,学习知识时抓核心要义。
    • 持续学习,知识内化,不断总结和输出。工作实践不断总结成经验和方法,形成自己的理论体系,揉碎成通俗易懂的生活例子阐释。
    • 寻求好的实践经历。在好的项目、工作环境中迸发出更多的灵感和提升。

     

    展开全文
  • 互联网产品灰度发布

    万次阅读 2016-05-30 14:37:39
    互联网产品灰度发布   关于2016年5月15日,DevOps成都站|架构与运维峰会活动总结 1. 前言 2 2. 灰度发布定义 5 3. 灰度发布作用 5 4. 灰度发布步骤 5 5. 灰度发布测试方法 6 6. 灰度发布引擎 6 7. 灰度...
  • 抛出std::bad_alloc,内存分配失败

    千次阅读 2013-04-24 10:33:40
    用C++的new操作符,分配内存,什么时候会失败?【转】 Effective C++ 中文第二版 50个有效改善程序设计效率的办法 条款7:预先准备好内存不够的情况 operator new在无法完成内存分配请求时会抛出异常(以前的做法...
  • 商业模式的定义——做产品到底是做什么   商业模式描述了企业如何创造价值、传递价值和获取价值的基本原理。本文将从商业模式的角度出发,探讨做产品到底是做什么这个话题。 产品经理到底是在干什么呢? 这...
  • 产品经理的主要工作职责

    千次阅读 2018-07-27 11:25:01
    到底什么产品经理?这个职位的主要职责是什么?在IT产业的不同领域,甚至在同一领域的不同公司,这个职位的定义似乎都有不同。 虽然在不同的公司,产品经理的角色和职责互有差异,但是有一些关键职责是任何一个...
  • GRASP通用职责分配软件模式

    千次阅读 2012-11-18 00:00:12
    它的核心思想是“职责分配(Responsibility Assignment)”。GRASP提出了几个基本原则,用来解决面向对象设计的一些问题。 Craig Larman在《Applying UML and Patterns》一书中提出了GRASP设计模式的概念。作者称其...
  • 单片机程序烧录的3种方式(ISP、ICP、IAP)是什么

    千次阅读 多人点赞 2021-04-25 23:59:37
    说起给单片机烧录程序,大家应该都不陌生吧...再后来,接触到产品研发,给已经发布出的产品升级,都是要靠远程无线升级的(想想看,产品已经到客户那里了,当软件需要升级时,要是还使用有线的方式烧录程序,得有多麻烦
  • 集成产品开发(IPD)

    千次阅读 2007-03-03 21:15:00
    集成产品开发(IPD) 集成产品开发(Integrated Product Development, 简称IPD)是一套产品开发的模式、理念与方法。IPD的思想来源于美国PRTM公司出版的《产品及生命周期优化法》(简称PACE——Product And Cycle-...
  • Intel英特尔历代经典 CPU 产品回顾

    万次阅读 2012-08-30 14:48:11
    悉数历史 英特尔历代经典 CPU 产品回顾 从英特尔于 1971 年推出首款 4004 微处理器到现在, 英特尔处理器已经走过 了 40 个年头。在告别 13 年传奇品牌奔腾之后,我们又迎来新一代酷睿 i 双核处 理器。现在,我们...
  • 在完成《启示录:打造用户喜爱的产品》一书之后(预计5 月上市),我们的翻译团队——七印部落,又开始翻译起各种好文章,并在《程序员》杂志上开设了专栏,条件是,网络发布要比杂志晚一个月,好在,我们不...
  • 详述银行卡支付方式

    万次阅读 2017-08-03 14:18:20
    博主说:在众多的支付方式中,银行卡支付是一种比较常见的支付方式, 其包括线下支付和线上支付两种,线下支付就是常见的 POS 机支付;线上支付主要为网银支付和快捷支付等。本文主要讲述了银行卡支付的几种方式以及...
  • SAP中关于联产品和副产品的处理

    千次阅读 2009-07-16 10:25:00
    产品在生产主要产品过程中附带生产出的非主要产品。两者的主要特点如下:1)联产品都是企业的主要产品,是企业生产活动的主要目标。;销售价格较高,对企业收入有较大贡献;要生产一种
  • c++五种内存分配、堆与栈区别

    千次阅读 2015-10-12 10:14:30
     栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。    堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序...
  • 产品读书」俞军产品方法论

    千次阅读 2017-05-31 19:36:22
    在阅读这本书之前,第一个能想到的同等的产品书籍就是网易的《幕后产品》,对于我来说,网易的产品一直是我最为钦佩和喜欢的,但是互联网界的产品名声最大的除了张小龙,我估计就是俞军了,读这本书之前我提前看了...
  • java程序运行时如何分配内存

    千次阅读 2015-09-18 17:52:41
    注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。 3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未...
  • 在(移动)互联网、大数据、、云计算等科技不断发展的背景下,对市场、对用户、对产品、对企业价值链乃至对这商业生态的进行重新审视的思考方式。    1.用户思维 对经营理念和消费者的理解。 用户思维...
  • std::bad_alloc 内存分配出错

    万次阅读 2014-06-30 11:50:36
    operator new在无法完成内存分配请求时会抛出异常(以前的做法一般是返回0,一些旧一点的编译器还这么做。你愿意的话也可以把你的编译器设置成这样。关于这个话题我将推迟到本条款的结尾处讨论)。大家都知道,处理...
  • 之前的动态内存分配,在上面总是莫名其妙的崩。不知道堆和栈空间该改多大合适。且总共64K的内存,太受限了。 几次想放弃,最后还是坚持了一下,终于搞定啦! 看miracl库官方说明文档,是使用了内存吃紧的设备的。...
  • 遭遇std::bad_alloc,内存分配失败

    万次阅读 2010-12-15 16:55:00
    用C++的new操作符,分配内存,什么时候会失败?【转】 Effective C++ 中文第二版 50个有效改善程序设计效率的办法 条款7:预先准备好内存不够的情况 operator new在无法完成内存分配请求时会抛...
  • SAP在产品和产成品分摊

    千次阅读 2015-04-07 16:26:31
    基本生产费用在产成品和在产品之间的分配 工业企业在结转完制造...在产品:又称在制品,ERP中有个洋文叫WIP,广义的在产品包括生产车间/生产工艺中正在加工的在产品和半成品, 狭义的在产品前者,俗称未完工产品
  • 产品经理——产品原型设计规范

    万次阅读 多人点赞 2018-08-29 14:35:42
    页面结构指产品原型设计工作界面的目标产品的页面结构,页面结构由原型说明和产品原型2部分组成,各部分使用文件夹分隔(如果产品包含多个子系统,则需要创建多个文件夹分隔)。 原型说明是辅助说明内容,方便体验...
  • Offer——知识点储备-J2EE基础

    万次阅读 多人点赞 2016-10-27 09:00:36
    Offer——知识点储备-J2EE基础9.2 jdk 1.8的新特性(核心是Lambda 表达式)参考链接:http://www.bubuko.com/infodetail-690646.html (1)接口的默认方法 (给接口添加一个非抽象的方法实现,只需default关键字...
  • 产品经理的七个阶段

    千次阅读 2017-12-19 11:40:29
    也就是基本处于一种被动分配任务的状态,接收的都是“二手需求”。一般而言自己思考产生的产品优化会比较少。也就是目前IT行业里面数百万的产品助理,甚至一些泛产品经理(从事类似工作,但不以产品经理为title的人...
  • 产品经理数据分析入门

    万次阅读 多人点赞 2017-11-20 11:47:25
    这篇文章一共会分为四个部分进行讲解。 认识数据 通过这部分我们可以知道什么是数据,数据与产品之间的关系等。...这里我们可以知道在做产品的时候,使用数据的一些场景和利用数据驱动产品的思维方式
  • 高并发是由于移动APP或网站PV(page view)即页面浏览量或点击量大,单台服务器无法承载大量访问所带来的压力,因此会采用服务器集群技术,用N台服务器进行分流,对于每次访问采取负载均衡策略,被分配到不同的...
  • SAP中关于联产品和副产品的处理

    千次阅读 2013-09-11 18:23:47
    产品用同一种原料,经过同一个生产过程,生产出两种或两种以上的不同性质和用途的产品; 副产品在生产主要产品过程中附带生产出的非主要产品。 两者的主要特点如下: 1)联产品都是企业的主要产品,是...
  • PACE(Product And Cycle-time Excellence)即产品及周期优化法,是IPD(Integrated Product Development,集成产品开发)的基础和前身。PACE实际体现的就是一种“流程”思想,是一种关于产品开发的流程框架,关注的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 105,180
精华内容 42,072
关键字:

产品分配方式指什么