精华内容
下载资源
问答
  • 网页可访问(超详细)安装element ui组件vue界面使用upload上传组件springboot后端保存静态资源以及请求地址编写保存静态资源类controller类编写注意 注意·······以下效果 安装element ui组件 详细...

    安装element ui组件

    详细方法参考官网
    如使用npm过慢或者失败可以使用cnpm安装

    vue界面使用upload上传组件

    参考官网代码,界面写上代码

    <template>
    	<div class="hello">
    		<el-upload
    			class="avatar-uploader"
    			action="http://localhost:8088/test/upload"
    			:show-file-list="false"
    			:on-success="handleAvatarSuccess"
    			:before-upload="beforeAvatarUpload"
    		>
    			<img v-if="imageUrl" :src="imageUrl" class="avatar" />
    			<i v-else class="el-icon-plus avatar-uploader-icon"></i>
    		</el-upload>
    	</div>
    </template>
    
    <script>
    export default {
    	name: 'HelloWorld',
    	data() {
    		return {
    			imageUrl: ''
    		};
    	}
    	,
    	 methods: {
          handleAvatarSuccess(res, file) {
            this.imageUrl = URL.createObjectURL(file.raw);
          },
    //       beforeAvatarUpload(file) {
    //         const isJPG = file.type === 'image/jpeg';
    //         const isLt2M = file.size / 1024 / 1024 < 2;
    // 
    //         if (!isJPG) {
    //           this.$message.error('上传头像图片只能是 JPG 格式!');
    //         }
    //         if (!isLt2M) {
    //           this.$message.error('上传头像图片大小不能超过 2MB!');
    //         }
    //         return isJPG && isLt2M;
    //       }
        }
    };
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
     .avatar-uploader .el-upload {
        border: 1px dashed #d9d9d9;
        border-radius: 6px;
        cursor: pointer;
        position: relative;
        overflow: hidden;
      }
      .avatar-uploader .el-upload:hover {
        border-color: #409EFF;
      }
      .avatar-uploader-icon {
        font-size: 28px;
        color: #8c939d;
        width: 178px;
        height: 178px;
        line-height: 178px;
        text-align: center;
      }
      .avatar {
        width: 178px;
        height: 178px;
        display: block;
      }
    </style>
    
    

    注意action为后端地址。
    其中下文file需要与后端名称统一

    handleAvatarSuccess(res, file) {
    this.imageUrl = URL.createObjectURL(file.raw);
    },
    界面实例

    springboot后端保存到静态资源以及请求地址编写

    保存到静态资源类

    package com.spring.controller;
    
    import java.io.File;
    import java.io.IOException;
    
    import org.springframework.web.multipart.MultipartFile;
    
    public class FileUploadUtil {
    	/**
         * 上传文件
         *
         * @param multipartFile
         * @return 文件存储路径
         */
        public static String upload(MultipartFile multipartFile) {
            // 文件存储位置,文件的目录要存在才行,可以先创建文件目录,然后进行存储
            String filePath = "F:/java Web/workspace/vueuplodimg/src/main/resources/static/img/" + multipartFile.getOriginalFilename();
            File file = new File(filePath);
            if (!file.exists()) {
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // 文件存储
            try {
                multipartFile.transferTo(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return file.getAbsolutePath();
        }
    }
    
    

    controller类编写

    跨域我前端没有弄,后端是允许所有访问。
    注意代码中的注释

    package com.spring.controller;
    
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;
    
    @RestController
    @RequestMapping(value = "/test")
    @CrossOrigin
    public class TestController {
    	@PostMapping(value = "/upload")
        // @RequestParam中的file名应与前端的值保持一致
        public String upload(@RequestParam("file") MultipartFile multipartFile) {
            // replaceAll 用来替换windows中的\\ 为 /
    		System.out.println("访问");
            return FileUploadUtil.upload(multipartFile).replaceAll("\\\\", "/");
        }
    
    }
    
    

    注意 注意·······

    我使用的是eclipse,图片保存到静态资源后需要手动刷新项目才可以,解决办法如下:
    点击window —>preferences —>General —>Workspace —>勾选Refresh using native hooks or polling 保存设置完美解决。
    idea请自行百度解决。
    方法二:使用热部署,自行百度

    以下为效果图

    前端图片
    后端接收到图片地址
    想要demo的可联系qq148 570 2004

    展开全文
  • 静态图转化分块加载的动态图 方案 1. PIL: 1. 创建背景图 2. 将原图拆分成N块并依次合成到背景图的相应位置, 得到N张素材图 3. 将N张素材图合成GIF 2. pygifsicle 对合成的GIF进行优化(无损压缩, 精简...

    简介

    将静态图转化为分块加载的动态图
    

    方案

    1. PIL: 
        1. 创建背景图
        2. 将原图拆分成N块并依次合成到背景图的相应位置, 得到N张素材图
        3. 将N张素材图合成GIF
    
    2. pygifsicle
        对合成的GIF进行优化(无损压缩, 精简体积)
        注意: 需要电脑安装gifsicle, 官网: https://www.lcdf.org/gifsicle/, 
        若看不懂英文, 网上资料一大把, (其实不安装也不影响正常使用, 只是没有优化GIF而已)
    
    3. tkinter:
        用于图形化界面的实现, 便于操作
        
    4. pyinstaller
        用于将脚本打包成exe
    

    源码

    https://gitee.com/tianshl/img2gif.git
    

    脚本介绍

    img2gif.py
    简介: 将图片转成gif 命令行模式
    使用: python img2gif.py -h  
    示例: python img2gif.py -p /Users/tianshl/Documents/sample.jpg
    
    img2gif_gui.py
    简介: 将图片转成gif 图像化界面
    使用: python img2gif_gui.py
    

    打包成exe

    pyinstaller -F -w -i gif.ico img2gif_gui.py
    # 执行完指令后, exe文件在dist目录下
    # 我打包的exe: https://download.csdn.net/download/xiaobuding007/12685554
    

    效果图

    命令行模式

    在这里插入图片描述

    图形化界面

    在这里插入图片描述

    在这里插入图片描述

    代码

    requirements.txt (依赖)
    Pillow==7.2.0
    pygifsicle==1.0.1
    
    img2gif.py (命令行模式 )
    # -*- coding: utf-8 -*-
    """
     **********************************************************
     * Author        : tianshl
     * Email         : xiyuan91@126.com
     * Last modified : 2020-07-29 14:58:57
     * Filename      : img2gif.py
     * Description   : 图片转动图
     * Documents     : https://www.lcdf.org/gifsicle/
     * ********************************************************
    """
    import argparse
    import copy
    import logging
    import os
    import random
    
    from PIL import Image
    from pygifsicle import optimize
    
    LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
    log = logging.getLogger(__name__)
    
    
    class Img2Gif:
        """
        图片转动图
        """
    
        def __init__(self, img_path, blocks=16, mode='append', random_block=False):
            """
            初始化
            :param img_path:        图片地址
            :param blocks:          分块数
            :param mode:            展示模式 append: 追加, flow: 流式, random: 随机
            :param random_block:    随机拆分
            """
            self.mode = mode if mode in ['flow', 'append', 'random'] else 'append'
    
            self.blocks = blocks
            self.random_block = random_block
    
            # 背景图
            self.img_background = None
    
            self.img_path = img_path
            self.img_dir, self.img_name = os.path.split(img_path)
            self.img_name = os.path.splitext(self.img_name)[0]
    
            self.gif_path = os.path.join(self.img_dir, '{}.gif'.format(self.img_name))
    
        def get_ranges(self):
            """
            获取横向和纵向块数
            """
            if not self.random_block:
                w = int(self.blocks ** 0.5)
                return w, w
    
            ranges = list()
            for w in range(2, int(self.blocks ** 0.5) + 1):
                if self.blocks % w == 0:
                    ranges.append((w, self.blocks // w))
    
            if ranges:
                return random.choice(ranges)
            else:
                return self.blocks, 1
    
        def materials(self):
            """
            素材
            """
    
            log.info('分割图片')
            img_origin = Image.open(self.img_path)
            (width, height) = img_origin.size
            self.img_background = Image.new(img_origin.mode, img_origin.size)
    
            # 单方向分割次数
            blocks_w, blocks_h = self.get_ranges()
    
            block_width = width // blocks_w
            block_height = height // blocks_h
    
            img_tmp = copy.copy(self.img_background)
            # 动图中的每一帧
            _materials = list()
            for h in range(blocks_h):
                for w in range(blocks_w):
                    block_box = (w * block_width, h * block_height, (w + 1) * block_width, (h + 1) * block_height)
                    block_img = img_origin.crop(block_box)
                    if self.mode in ['flow', 'random']:
                        img_tmp = copy.copy(self.img_background)
                    img_tmp.paste(block_img, (w * block_width, h * block_height))
                    _materials.append(copy.copy(img_tmp))
    
            # 随机打乱顺序
            if self.mode == 'random':
                random.shuffle(_materials)
    
            log.info('分割完成')
            # 最后十帧展示原图
            [_materials.append(copy.copy(img_origin)) for _ in range(10)]
            return _materials
    
        def gif(self):
            """
            合成gif
            """
    
            materials = self.materials()
            log.info('合成GIF')
            self.img_background.save(self.gif_path, save_all=True, loop=True, append_images=materials, duration=250)
            log.info('合成完成')
    
            log.info('压缩GIF')
            optimize(self.gif_path)
            log.info('压缩完成')
    
    
    if __name__ == '__main__':
        parser = argparse.ArgumentParser()
        parser.add_argument("-p", "--img_path", required=True, help="图片路径")
        parser.add_argument("-b", "--blocks", type=int, default=16, help="块数")
        parser.add_argument("-r", "--random_block", type=bool, default=False, help="随机拆分块数")
        parser.add_argument(
            '-m', '--mode', default='append', choices=['append', 'flow', 'random'],
            help="块展示模式 append: 追加, flow: 流式, random: 随机"
        )
        args = parser.parse_args()
    
        Img2Gif(**args.__dict__).gif()
    
    
    img2gif_gui.py (图形化界面)
    # -*- coding: utf-8 -*-
    """
     **********************************************************
     * Author        : tianshl
     * Email         : xiyuan91@126.com
     * Last modified : 2020-07-29 14:58:57
     * Filename      : img2gif_gui.py
     * Description   : 图片转动图
     * Documents     : https://www.lcdf.org/gifsicle/
     * ********************************************************
    """
    import copy
    import random
    from tkinter import *
    from tkinter import ttk, messagebox
    from tkinter.filedialog import askopenfilename, asksaveasfilename
    
    from PIL import Image, ImageTk
    from pygifsicle import optimize
    
    
    class Img2Gif(Frame):
        """
        图形化界面
        """
    
        def __init__(self):
            """
            初始化
            """
            Frame.__init__(self)
    
            # 设置窗口信息
            self.__set_win_info()
    
            # 渲染窗口
            self._gif_pane = None
            self.__render_pane()
    
        def __set_win_info(self):
            """
            设置窗口信息
            """
            # 获取屏幕分辨率
            win_w = self.winfo_screenwidth()
            win_h = self.winfo_screenheight()
            # 设置窗口尺寸/位置
            self._width = 260
            self._height = 300
            self.master.geometry('{}x{}+{}+{}'.format(
                self._width, self._height, (win_w - self._width) // 2, (win_h - self._height) // 2)
            )
            # 设置窗口不可变
            self.master.resizable(width=False, height=False)
    
        @staticmethod
        def __destroy_frame(frame):
            """
            销毁frame
            """
            if frame is None:
                return
    
            for widget in frame.winfo_children():
                widget.destroy()
    
            frame.destroy()
    
        def __render_pane(self):
            """
            渲染窗口
            """
    
            self._main_pane = Frame(self.master, width=self._width, height=self._height)
            self._main_pane.pack()
    
            # 设置窗口标题
            self.master.title('图片转GIF')
    
            # 选择图片
            image_path_label = Label(self._main_pane, text='选择图片', relief=RIDGE, padx=10)
            image_path_label.place(x=10, y=10)
    
            self._image_path_entry = Entry(self._main_pane, width=13)
            self._image_path_entry.place(x=90, y=7)
    
            image_path_button = Label(self._main_pane, text='···', relief=RIDGE, padx=5)
            image_path_button.bind('<Button-1>', self.__select_image)
            image_path_button.place(x=220, y=10)
    
            # 拆分块数
            blocks_label = Label(self._main_pane, text='拆分块数', relief=RIDGE, padx=10)
            blocks_label.place(x=10, y=50)
    
            self._blocks_scale = Scale(
                self._main_pane, from_=2, to=100, orient=HORIZONTAL, sliderlength=10
            )
            self._blocks_scale.set(16)
            self._blocks_scale.place(x=90, y=33)
    
            Label(self._main_pane, text='(块)').place(x=200, y=50)
    
            # 随机拆分
            random_block_label = Label(self._main_pane, text='随机拆分', relief=RIDGE, padx=10)
            random_block_label.place(x=10, y=90)
    
            self._random_block = BooleanVar(value=False)
            random_block_check_button = ttk.Checkbutton(
                self._main_pane, variable=self._random_block,
                width=0, onvalue=True, offvalue=False
            )
            random_block_check_button.place(x=90, y=90)
    
            # 动图模式
            mode_label = Label(self._main_pane, text='动图模式', relief=RIDGE, padx=10)
            mode_label.place(x=10, y=130)
    
            self._mode = StringVar(value='append')
            ttk.Radiobutton(self._main_pane, text='追加', variable=self._mode, value='append').place(x=90, y=130)
            ttk.Radiobutton(self._main_pane, text='流式', variable=self._mode, value='flow').place(x=145, y=130)
            ttk.Radiobutton(self._main_pane, text='随机', variable=self._mode, value='random').place(x=200, y=130)
    
            # 每帧延时
            duration_label = Label(self._main_pane, text='每帧延时', relief=RIDGE, padx=10)
            duration_label.place(x=10, y=170)
            self._duration_scale = Scale(
                self._main_pane, from_=50, to=1000, orient=HORIZONTAL, sliderlength=10
            )
            self._duration_scale.set(250)
            self._duration_scale.place(x=90, y=152)
    
            Label(self._main_pane, text='(毫秒)').place(x=200, y=170)
    
            # 整图帧数
            whole_frames_label = Label(self._main_pane, text='整图帧数', relief=RIDGE, padx=10)
            whole_frames_label.place(x=10, y=210)
    
            self._whole_frames_scale = Scale(
                self._main_pane, from_=0, to=20, orient=HORIZONTAL, sliderlength=10
            )
            self._whole_frames_scale.set(10)
            self._whole_frames_scale.place(x=90, y=193)
    
            Label(self._main_pane, text='(帧)').place(x=200, y=210)
    
            # 开始转换
            execute_button = ttk.Button(self._main_pane, text='开始执行', width=23, command=self.__show_gif)
            execute_button.place(x=10, y=250)
    
        def __select_image(self, event):
            """
            选择图片
            """
            image_path = askopenfilename(title='选择图片', filetypes=[
                ('PNG', '*.png'), ('JPG', '*.jpg'), ('JPG', '*.jpeg'), ('BMP', '*.bmp'), ('ICO', '*.ico')
            ])
            self._image_path_entry.delete(0, END)
            self._image_path_entry.insert(0, image_path)
    
        def __block_ranges(self):
            """
            获取图片横向和纵向需要拆分的块数
            """
            blocks = self._blocks_scale.get()
            if not self._random_block.get():
                n = int(blocks ** 0.5)
                return n, n
    
            ranges = list()
            for horizontally in range(1, blocks + 1):
                if blocks % horizontally == 0:
                    ranges.append((horizontally, blocks // horizontally))
    
            if ranges:
                return random.choice(ranges)
            else:
                return blocks, 1
    
        def __generate_materials(self):
            """
            根据原图生成N张素材图
            """
            image_path = self._image_path_entry.get()
            if not image_path:
                messagebox.showerror(title='错误', message='请选择图片')
                return
            self._image_origin = Image.open(image_path)
    
            # 获取图片分辨率
            (width, height) = self._image_origin.size
    
            # 创建底图
            self._image_background = Image.new(self._image_origin.mode, self._image_origin.size)
            image_tmp = copy.copy(self._image_background)
    
            # 获取横向和纵向块数
            horizontally_blocks, vertically_blocks = self.__block_ranges()
    
            # 计算每块尺寸
            block_width = width // horizontally_blocks
            block_height = height // vertically_blocks
    
            width_diff = width - block_width * horizontally_blocks
            height_diff = height - block_height * vertically_blocks
    
            # GIF模式
            gif_mode = self._mode.get()
            # 生成N帧图片素材
            materials = list()
            for v_idx, v in enumerate(range(vertically_blocks)):
                for h_idx, h in enumerate(range(horizontally_blocks)):
                    _block_width = (h + 1) * block_width
                    # 最右一列 宽度+误差
                    if h_idx + 1 == horizontally_blocks:
                        _block_width += width_diff
    
                    _block_height = (v + 1) * block_height
                    # 最后一行 高度+误差
                    if v_idx + 1 == vertically_blocks:
                        _block_height += height_diff
    
                    block_box = (h * block_width, v * block_height, _block_width, _block_height)
                    block_img = self._image_origin.crop(block_box)
                    if gif_mode in ['flow', 'random']:
                        image_tmp = copy.copy(self._image_background)
                    image_tmp.paste(block_img, (h * block_width, v * block_height))
                    materials.append(copy.copy(image_tmp))
    
            # mode=random时随机打乱顺序
            if gif_mode == 'random':
                random.shuffle(materials)
    
            # 整图帧数
            [materials.append(copy.copy(self._image_origin)) for _ in range(self._whole_frames_scale.get())]
    
            return materials
    
        def __show_gif(self):
            """
            展示GIF
            """
    
            self._materials = self.__generate_materials()
            if not self._materials:
                return
    
            self._main_pane.place(x=0, y=-1 * self._height)
            self._gif_pane = Frame(self.master, width=self._width, height=self._height)
            self._gif_pane.pack()
    
            # 设置窗口标题
            self.master.title('预览GIF')
    
            label_width = 240
            label = Label(self._gif_pane, width=label_width, height=label_width)
            label.place(x=8, y=5)
    
            button_save = ttk.Button(self._gif_pane, text='保存', width=9, command=self.__save_gif)
            button_save.place(x=8, y=250)
    
            button_cancel = ttk.Button(self._gif_pane, text='返回', width=9, command=self.__show_main_pane)
            button_cancel.place(x=138, y=250)
    
            # 尺寸
            (width, height) = self._image_origin.size
            # 帧速
            duration = self._duration_scale.get()
            # 缩放
            gif_size = (label_width, int(height / width * label_width))
    
            frames = [ImageTk.PhotoImage(img.resize(gif_size, Image.ANTIALIAS)) for img in self._materials]
            # 帧数
            idx_max = len(frames)
    
            def show(idx):
                """
                展示图片
                """
                frame = frames[idx]
                label.configure(image=frame)
                idx = 0 if idx == idx_max else idx + 1
                self._gif_pane.after(duration, show, idx % idx_max)
    
            show(0)
    
        def __save_gif(self):
            """
            存储GIF
            """
            gif_path = asksaveasfilename(title='保存GIF', filetypes=[('GIF', '.gif')])
            if not gif_path:
                return
    
            gif_path += '' if gif_path.endswith('.gif') or gif_path.endswith('.GIF') else '.gif'
            # 存储GIF
            Image.new(self._image_origin.mode, self._image_origin.size).save(
                gif_path, save_all=True, loop=True, duration=self._duration_scale.get(), append_images=self._materials
            )
    
            # 优化GIF
            optimize(gif_path)
            messagebox.showinfo(title='提示', message='保存成功')
    
            self.__show_main_pane()
    
        def __show_main_pane(self):
            """
            取消保存
            """
            self.__destroy_frame(self._gif_pane)
            self._main_pane.place(x=0, y=0)
    
    
    if __name__ == '__main__':
        Img2Gif().mainloop()
    
    
    展开全文
  • 静态链接使得不同的程序开发者和部门能够相对独立地开发和测试自己的程序模块,从某种意义上来讲大大促进了程序开发的效率,原先限制程序的规模也随之扩大。但是慢慢地静态链接的诸多缺点也逐步暴露出来,比如浪费...

    本文摘抄于程序员的自我修养-链接装载与库7.1节,这段写的很好,直接拿过来来收藏
    http://www.wq3028.top/technology/compile/20180727124/

    静态链接使得不同的程序开发者和部门能够相对独立地开发和测试自己的程序模块,从某种意义上来讲大大促进了程序开发的效率,原先限制程序的规模也随之扩大。但是慢慢地静态链接的诸多缺点也逐步暴露出来,比如浪费内存和磁盘空间、模块更新困难等问题,使得人们不得不寻找一种更好的方式来组织程序的模块。

    内存和磁盘空间
    静态链接这种方法的确很简单,原理上很容易理解,实践上很难实现,在操作系统和硬件不发达的早期,绝大部分系统采用这种方案。随着计算机软件的发展,这种方法的缺点很快就暴露出来了,那就是静态连接的方式对于计算机内存和磁盘的空间浪费非常严重。特别是多进程操作系统情况下,静态链接极大地浪费了内存空间,想象一下每个程序内部除了都保留着printf()函数、scanf()函数、strlen()等这样的公用库函数,还有数量相当可观的其他库函数及它们所需要的辅助数据结构。在现在的Linux系统中,一个普通程序会使用到的C语言静态库至少在1 MB以上,那么,如果我们的机器中运行着100个这样的程序,就要浪费近100 MB的内存;如果磁盘中有2 000个这样的程序,就要浪费近2 GB的磁盘空间,很多Linux的机器中,/usr/bin下就有数千个可执行文件。

    比如图7-1所示的Program1和Program2分别包含Program1.o和Program2.o两个模块,并且它们还共用Lib.o这两模块。在静态连接的情况下,因为Program1和Program2都用到了Lib.o这个模块,所以它们同时在链接输出的可执行文件Program1和Program2有两个副本。当我们同时运行Program1和Program2时,Lib.o在磁盘中和内存中都有两份副本。当系统中存在大量的类似于Lib.o的被多个程序共享的目标文件时,其中很大一部分空间就被浪费了。在静态链接中,C语言静态库是很典型的浪费空间的例子,还有其他数以千计的库如果都需要静态链接,那么空间浪费无法想象。

    这里写图片描述
    图7-1 静态链接时文件在内存中的副本

    程序开发和发布
    空间浪费是静态链接的一个问题,另一个问题是静态链接对程序的更新、部署和发布也会带来很多麻烦。比如程序Program1所使用的Lib.o是由一个第三方厂商提供的,当该厂商更新了Lib.o的时候(比如修正了lib.o里面包含的一个Bug),那么Program1的厂商就需要拿到最新版的Lib.o,然后将其与Program1.o链接后,将新的Program1整个发布给用户。这样做的缺点很明显,即一旦程序中有任何模块更新,整个程序就要重新链接、发布给用户。比如一个程序有20个模块,每个模块1 MB,那么每次更新任何一个模块,用户就得重新获取这个20 MB的程序。如果程序都使用静态链接,那么通过网络来更新程序将会非常不便,因为一旦程序任何位置的一个小改动,都会导致整个程序重新下载。

    动态链接
    要解决空间浪费和更新困难这两个问题最简单的办法就是把程序的模块相互分割开来,形成独立的文件,而不再将它们静态地链接在一起。简单地讲,就是不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是说,把链接这个过程推迟到了运行时再进行,这就是动态链接(Dynamic Linking)的基本思想。

    还是以Program1和Program2为例,假设我们保留Program1.o、Program2.o和Lib.o三个目标文件。当我们要运行Program1这个程序时,系统首先加载Program1.o,当系统发现Program1.o中用到了Lib.o,即Program1.o依赖于Lib.o,那么系统接着加载Lib.o,如果Program1.o或Lib.o还依赖于其他目标文件,系统会按照这种方法将它们全部加载至内存。所有需要的目标文件加载完毕之后,如果依赖关系满足,即所有依赖的目标文件都存在于磁盘,系统开始进行链接工作。这个链接工作的原理与静态链接非常相似,包括符号解析、地址重定位等,我们在前面已经很详细地介绍过了。完成这些步骤之后,系统开始把控制权交给Program1.o的程序入口处,程序开始运行。这时如果我们需要运行Program2,那么系统只需要加载Program2.o,而不需要重新加载Lib.o,因为内存中已经存在了一份Lib.o的副本(见图7-2),系统要做的只是将Program2.o和Lib.o链接起来。很明显,上面的这种做法解决了共享的目标文件多个副本浪费磁盘和内存空间的问题,可以看到,磁盘和内存中只存在一份Lib.o,而不是两份。另外在内存中共享一个目标文件

    这里写图片描述

    图7-2 动态链接时文件在内存中的副本

    模块的好处不仅仅是节省内存,它还可以减少物理页面的换入换出,也可以增加CPU缓存的命中率,因为不同进程间的数据和指令访问都集中在了同一个共享模块上。上面的动态链接方案也可以使程序的升级变得更加容易,当我们要升级程序库或程序共享的某个模块时,理论上只要简单地将旧的目标文件覆盖掉,而无须将所有的程序再重新链接一遍。当程序下一次运行的时候,新版本的目标文件会被自动装载到内存并且链接起来,程序就完成了升级的目标。当一个程序产品的规模很大的时候,往往会分割成多个子系统及多个模块,每个模块都由独立的小组开发,甚至会使用不同的编程语言。动态链接的方式使得开发过程中各个模块更加独立,耦合度更小,便于不同的开发者和开发组织之间独立进行开发和测试。

    程序可扩展性和兼容性
    动态链接还有一个特点就是程序在运行时可以动态地选择加载各种程序模块,这个优点就是后来被人们用来制作程序的插件(Plug-in)。比如某个公司开发完成了某个产品,它按照一定的规则制定好程序的接口,其他公司或开发者可以按照这种接口来编写符合要求的动态链接文件。该产品程序可以动态地载入各种由第三方开发的模块,在程序运行时动态地链接,实现程序功能的扩展。动态链接还可以加强程序的兼容性。一个程序在不同的平台运行时可以动态地链接到由操作系统提供的动态链接库,这些动态链接库相当于在程序和操作系统之间增加了一个中间层,从而消除了程序对不同平台之间依赖的差异性。比如操作系统A和操作系统B对于printf()的实现制不同,如果我们的程序是静态链接的,那么程序需要分别链接成能够在A运行和在B运行的两个版本并且分开发布;但是如果是动态链接,只要操作系统A和操作系统B都能提供一个动态链接库包含printf(),并且这个printf()使用相同的接口,那么程序只需要有一个版本,就可以在两个操作系统上运行,动态地选择相应的printf()的实现版本。当然这只是理论上的可能性,实际上还存在不少问题,我们会在后面继续探讨关于动态链接模块之间兼容性的问题。

    从上面的描述来看,动态链接是不是一种“万能膏药”,包治百病呢?很遗憾,动态链接也有诸多的问题及令人烦恼和费解的地方。很常见的一个问题是,当程序所依赖的某个模块更新后,由于新的模块与旧的模块之间接口不兼容,导致了原有的程序无法运行。这个问题在早期的Windows版本中尤为严重,因为它们缺少一种有效的共享库版本管理机制,使得用户经常出现新程序安装完之后,其他某个程序无法正常工作的现象,这个问题也经常被称为“DLLHell”。

    动态链接的基本实现
    动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有的程序模块都链接成一个个单独的可执行文件。那么我们能不能按照前面例子中所描述的那样,直接使用目标文件进行动态链接呢?这个问题的答案是:理论上是可行的,但实际上动态链接的实现方案与直接使用目标文件稍有差别。我们将在后面分析目标文件和动态链接文件的区别。动态链接涉及运行时的链接及多个文件的装载,必需要有操作系统的支持,因为动态链接的情况下,进程的虚拟地址空间的分布会比静态链接情况下更为复杂,还有一些存储管理、内存共享、进程线程等机制在动态链接下也会有一些微妙的变化。目前主流的操作系统几乎都支持动态链接这种方式,在Linux系统中,ELF动态链接文件被称为动态共享对象(DSO,Dynamic Shared Objects),简称共享对象,它们一般都是以“.so”为扩展名的一些文件;而在Windows系统中,动态链接文件被称为动态链接库(Dynamical Linking Library),它们通常就是我们平时很常见的以“.dll”为扩展名的文件。

    从本质上讲,普通可执行程序和动态链接库中都包含指令和数据,这一点没有区别。在使用动态链接库的情况下,程序本身被分为了程序主要模块(Program1)和动态链接库(Lib.so),但实际上它们都可以看作是整个程序的一个模块,所以当我们提到程序模块时可以指程序主模块也可以指动态链接库。在Linux中,常用的C语言库的运行库glibc,它的动态链接形式的版本保存在“/lib”目录下,文件名叫做“libc.so”。整个系统只保留一份C语言库的动态链接文件“libc.so”,而所有的C语言编写的、动态链接的程序都可以在运行时使用它。当程序被装载的时候,系统的动态链接器会将程序所需要的所有动态链接库(最基本的就是libc.so)装载到进程的地址空间,并且将程序中所有未决议的符号绑定到相应的动态链接库中,并进行重定位工作。程序与libc.so之间真正的链接工作是由动态链接器完成的,而不是由我们前面看到过的静态链接器ld完成的。也就是说,动态链接是把链接这个过程从本来的程序装载前被推迟到了装载的时候。可能有人会问,这样的做法的确很灵活,但是程序每次被装载时都要进行重新进行链接,是不是很慢?的确,动态链接会导致程序在性能的一些损失,但是对动态链接的链接过程可以进行优化,比如我们后面要介绍的延迟绑定(Lazy Binding)等方法,可以使得动态链接的性能损失尽可能地减小。据估算,动态链接与静态链接相比,性能损失大约在5%以下。当然经过实践的证明,这点性能损失用来换取程序在空间上的节省和程序构建和升级时的灵活性,是相当值得的。

    展开全文
  • 深度学习框架:动态图 vs 静态图

    万次阅读 多人点赞 2019-04-04 11:01:31
    动态图框架对应的是 命令式编程 静态图框架对应的是 符号式编程 命令式编程 vs 符号式编程 参考 https://blog.csdn.net/z0n1l2/article/details/80873608 ...

    根据MxNet深度学习框架官方文档 (https://mxnet.incubator.apache.org/versions/master/architecture/program_model.html#symbolic-vs-imperative-programs ), 从命令式编程和符号式编程的角度分析动态图和静态图深度学习框架的实现方式和特点。

    • 动态图框架对应的是 命令式编程
    • 静态图框架对应的是 符号式编程
    什么是symblic/imperative style编程

    使用过python或C++对imperative programs比较了解。imperative-stype programs在运行时计算,大部分python代码都是imperative。比如下面的例子:

      import numpy as np
      a = np.ones(10)
      b = np.ones(10) * 2
      c = b * a
      d = c + 1
    

    当程序执行到 c=bac=b∗a 时,代码开始做对应的数值计算。

    symbolic programs与此不同。symbolic-stype program中,需要先给出一个函数的定义(可能十分复杂)。当我们定义这个函数时,并不会做真正的数值计算。这类函数的定义中使用数值占位符,当给定真正的输入后,才会对这个函数进行编译计算。上面的例子用symbolic-stype program重新写:

        A = Variable('A')
        B = Variable('B')
        C = B * A
        D = C + Constant(1)
        # compiles the function
        f = compile(D)
        d = f(A=np.ones(10), B=np.ones(10)*2)
    

    上述代码中,语句 C=BAC=B∗A 并不会触发真正的数值计算,但会生成一个计算图(也称symbolic graph)描述这个计算。下图时计算D的计算图

    在这里插入图片描述
    大部分symbolic-style program都显性或隐性的包含一个编译的步骤,把计算转换成可以调用的函数。上面的例子中,数值计算仅仅在代码最后一行进行,symbolic program一个重要特点是其明确有构建计算图和生成可执行代码两个步骤。对于神经网络,一般会用一个就算图描述整个模型。

    其他流行深度学习框架中,PyTorch/Chainer/Minerva使用imperative style。symbolic-styple的框架包括Theano/CGT/TensorFlow。 CXXNet/Caffe这一类依赖配置文件的框架也看作symbolic-style库。此时配置文件被当作计算图的定义。下面我们对比一下二者的优劣:

    命令式编程更加灵活

    用python调用imperative-style库十分简单,编写方式和普通的python代码一样,在合适的位置调用库的代码实现加速。如果用python调用symbolic-style库,代码结构将出现一些变化,比如iteration可能无法使用。尝试把下面的例子转换成symbolic-style

        a = 2
        b = a + 1
        d = np.zeros(10)
        for i in range(d):
            d += np.zeros(10)
    

    如果symblic-style API不支持for循环,转换就没那个直接。不能用python的编码思路调用symblic-style库。需要利用symblic API定义的domain-specific-language(DSL)。深度学习框架会提供功能强大的DSL,把神经网络转化成可被调用的计算图。

    感觉上imperative program更加符合习惯,使用更加简单。例如,可以在任何位置打印出变量的值,轻松使用符合习惯的流程控制语句和循环语句。

    符号式编程更加高效

    既然imperative pragrams更加灵活,和计算机原生语言更加贴合,那么为什么很多深度学习框架使用symbolic风格? 最主要的原因式效率,内存效率和计算效率都很高。比如下面的例子:

        import numpy as np
        a = np.ones(10)
        b = np.ones(10) * 2
        c = b * a
        d = c + 1
        ...
    

    在这里插入图片描述
    如果数组每个元素内存中占据8字节,在python中需要多少内存?
    对于imperative programs中,需要在每一行上都分配必要的内存。一共4个数组,每个数组10个元素,一共4∗10∗8=320字节。 如果事先知道只有d是需要的结果,构造计算图时可以重复利用一些中间变量的空间。比如利用原址计算,我们可以把b的内存借给c使用,同样c的内存可以给d用,如此可以节省一半内存,仅仅需要2∗10∗8=160字节。
    symbolic programs限制更多,因为只需要d,构建计算图后,一些中间量,比如c,的值将无法看到。

    通过symbolic program,使用原址计算可以安全的重用内存,但牺牲了对c的访问可能。 imperative program可以处理各种访问可能,如果在python执行上述例子,任何中间量都可以方便访问。

    symbolic program还可以通过operation folding优化计算。在上述的例子中,乘法和加法可以展成一个操作,如下图所示:
    在这里插入图片描述

    如果在GPU上运算,计算图只需要一个kernel,节省了一个kernel。在很多优化库,比如caffe/CXXNet,人工编码进行此类优化操作。 operation folding可以提高计算效率。

    imperative program中不能自动operation folding,因为不知道中间变量是否会被访问到。symbolic program中可以做operation folding,因为获得了完整的计算图,而且明确哪些量以后会被访问,哪些量以后都不会被访问。

    Backprop和AutoDiff的案例分析

    在这一节,我们将基于自动微分或是反向传播的问题对比两种编程模式。梯度计算几乎是所有深度学习库所要解决的问题。使用命令式程序和符号式程序都能实现梯度计算。

    我们先看命令式程序。下面这段代码实现自动微分运算,我们之前讨论过这个例子。

        class array(object) :
            """Simple Array object that support autodiff."""
            def __init__(self, value, name=None):
                self.value = value
                if name:
                    self.grad = lambda g : {name : g}
    
            def __add__(self, other):
                assert isinstance(other, int)
                ret = array(self.value + other)
                ret.grad = lambda g : self.grad(g)
                return ret
    
            def __mul__(self, other):
                assert isinstance(other, array)
                ret = array(self.value * other.value)
                def grad(g):
                    x = self.grad(g * other.value)
                    x.update(other.grad(g * self.value))
                    return x
                ret.grad = grad
                return ret
    
        # some examples
        a = array(1, 'a')
        b = array(2, 'b')
        c = b * a
        d = c + 1
        print d.value
        print d.grad(1)
        # Results
        # 3
        # {'a': 2, 'b': 1}
    

    在上述程序里,每个数组对象都含有grad函数(事实上是闭包-closure)。当我们执行d.grad时,它递归地调用grad函数,把梯度值反向传播回来,返回每个输入值的梯度值。看起来似乎有些复杂。让我们思考一下符号式程序的梯度计算过程。下面这段代码是符号式的梯度计算过程。

        A = Variable('A')
        B = Variable('B')
        C = B * A
        D = C + Constant(1)
        # get gradient node.
        gA, gB = D.grad(wrt=[A, B])
        # compiles the gradient function.
        f = compile([gA, gB])
        grad_a, grad_b = f(A=np.ones(10), B=np.ones(10)*2)
    

    D的grad函数生成一幅反向计算图,并且返回梯度节点gA和gB。它们对应于下图的红点。

    在这里插入图片描述
    命令式程序做的事和符号式的完全一致。它隐式地在grad闭包里存储了一张反向计算图。当执行d.grad时,我们从d(D)开始计算,按照图回溯计算梯度并存储结果。

    因此我们发现无论符号式还是命令式程序,它们计算梯度的模式都一致。那么两者的差异又在何处?再回忆一下命令式程序“未雨绸缪”的要求。如果我们准备一个支持自动微分的数组库,需要保存计算过程中的grad闭包。这就意味着所有历史变量不能被垃圾回收,因为它们通过函数闭包被变量d所引用。那么,若我们只想计算d的值,而不想要梯度值该怎么办呢?

    在符号式程序中,我们声明f=compiled([D>)来替换。它也声明了计算的边界,告诉系统我只想计算正向通路的结果。那么,系统就能释放之前结果的存储空间,并且共享输入和输出的内存。

    假设现在我们运行的不是简单的示例,而是一个n层的深度神经网络。如果我们只计算正向通路,而不用反向(梯度)通路,我们只需分配两份临时空间存放中间层的结果,而不是n份。由于命令式程序需要为今后可能用到的梯度值做准备,中间结果不得不保存,就需要用到n份临时空间。

    正如我们所见,优化的程度取决于对用户行为的约束。符号式程序的思路就是让用户通过编译明确地指定计算的边界。而命令式程序为之后所有情况做准备。符号式程序更充分地了解用户需要什么和不想要什么,这是它的天然优势。

    当然,我们也能对命令式程序施加约束条件。例如,上述问题的解决方案之一是引入一个上下文变量。我们可以引入一个没有梯度的上下文变量,来避免梯度值的计算。这给命令式程序带来了更多的约束条件,以换取性能上的改善。

        with context.NoGradient():
            a = array(1, 'a')
            b = array(2, 'b')
            c = b * a
            d = c + 1
    

    然而,上述的例子还是有许多可能的未来,也就是说不能在正向通路中做同址计算来重复利用内存(一种减少GPU内存的普遍方法)。这一节介绍的技术产生了显式的反向通路。在Caffe和cxxnet等工具包里,反向传播是在同一幅计算图内隐式完成的。这一节的讨论同样也适用于这些例子。
    大多数基于函数库(如cxxnet和caffe)的配置文件,都是为了一两个通用需求而设计的。计算每一层的激活函数,或是计算所有权重的梯度。这些库也面临同样的问题,若一个库能支持的通用计算操作越多,我们能做的优化(内存共享)就越少,假设都是基于相同的数据结构。

    因此经常能看到一些例子在约束性和灵活性之间取舍。

    模型检查点

    支持对配置文件设置检查点是符号式程序的加分项。因为符号式的模型构建阶段并不包含计算步骤,我们可以直接序列化计算图,之后再重新加载它,无需引入附加层就解决了保存配置文件的问题。

        A = Variable('A')
        B = Variable('B')
        C = B * A
        D = C + Constant(1)
        D.save('mygraph')
        ...
        D2 = load('mygraph')
        f = compile([D2])
        # more operations
        ...
    

    因为命令式程序逐行执行计算。我们不得不把整块代码当做配置文件来存储,或是在命令式语言的顶部再添加额外的配置层。

    参数更新

    大多数符号式编程属于数据流(计算)图。数据流图能方便地描述计算过程。然而,它对参数更新的描述并不方便,因为参数的更新会引起变异(mutation),这不属于数据流的概念。大多数符号式编程的做法是引入一个特殊的更新语句来更新程序的某些持续状态。

    用命令式风格写参数更新往往容易的多,尤其是当需要相互关联地更新时。对于符号式编程,更新语句也是被我们调用并执行。在某种意义上来讲,目前大部分符号式深度学习库也是退回命令式方法进行更新操作,用符号式方法计算梯度。

    没有严格的边界

    我们已经比较了两种编程风格。之前的一些说法未必完全准确,两种编程风格之间也没有明显的边界。例如,我们可以用Python的(JIT)编译器来编译命令式程序,使我们获得一些符号式编程对全局信息掌握的优势。但是,之前讨论中大部分说法还是正确的,并且当我们开发深度学习库时这些约束同样适用。

    参考
    1. https://blog.csdn.net/z0n1l2/article/details/80873608
    2. https://mxnet.incubator.apache.org/versions/master/architecture/program_model.html#symbolic-vs-imperative-programs
    3. https://blog.csdn.net/daslab/article/details/50434145
    4. https://www.jianshu.com/p/1c7ef1ce5540 (MXNet的动态图接口Gluon
    展开全文
  • 最近有同学问我如何实现MFC基于对话框在图片控件中加载图片?其实使用MFC显示图片的方法各种各样,但是还是有些同学不知道怎样显示.以前在《数字图像处理》课程中完成的软件都是基于单文档的程序,这里介绍两种在对话框...
  • Android 显示Gif动态图和静态图

    千次阅读 2015-12-02 14:47:23
    Android 显示Gif动态图和静态图今天写了个demo来显示网络图片静态图都好说,很容易就可以显示,但是动态图呢?安卓里是没有自带的控件来显示gif图的,开发大神们可以自己写View来支持gif,我这边是用了开源的控件,...
  • 一键把动态IP自动设置为静态IP

    千次阅读 2018-05-15 22:02:37
    一键把动态IP自动设置为静态IP 问题描述 平时在调试网络模块时,常常需要手动设置IP,然后才可以开始调试网络模块。调试完之后又需要手动设置IP为动态获取。一直就像用批处理来解决,但是一直懒没做,现在来回改...
  • 静态图动态图的优劣

    万次阅读 2019-06-25 21:21:00
    静态图可以在磁盘中序列化,可以保存整个网络的结构,可以重载,在部署中很实用 动态图则需要重复之前的代码 动态图相比静态图代码更简洁 在tensorflow静态图中条件和循环需要特定的语法,pytorch只用...
  • 动态页面转化为静态页面

    千次阅读 2017-04-20 00:41:14
    2 让jsp输出在指定的文件下的静态页面, 3 最后又重定向到当前项目路径下的,静态页面 向对方的浏览器传输html文件的相关重要代码如下: 有关filter的代码: private FilterConfig config=null; public void ...
  • 2、网页内容一经发布到网站服务器上,无论是否有用户访问,每个静态网页的内容都是保存在网站服务器上的,也就是说,静态网页是实实在在保存在服务器上的文件,每个网页都是一个独立的文件;3、静态网页的内容相对...
  • 故作者本次分享一下基于bezierMaker.js实现的将静态图片按照自定义曲线轨迹扭曲图片并合称为动态效果。欢迎关注我的博客,不定期更新中——效果预览之前的描述可能不是很清楚我们直接看下效果:首先加载一张: ...
  • 第一需要在项目下手动创建一个static 的文件目录,然后我们在setting.py 中 配置一个路径 用来保存 图片: !表示项目根路径下的static文件夹下的upload文件夹 UPLOADFILES_DIRS = os.path.join(BASE_DI...
  • 所谓"动态",并不是指在网页中放几个带动画的GIF或者SWF图片和动画,而是指用户在浏览此网页时,可以根据本身的需求在网页中进行操作,而网页根据用户的输入,能够产生相应的结果来响应用户,那样的网页才叫动态网页...
  • 合并网格的目的 为了方便Call Drall 静、动态批处理优化 为了方便模型部分更换、换装 合并网格的几种方法 ...所有被设置为静态的物体都会被合并 【方法&步骤】 点击物体 -> Inspector窗口 -> static ...
  • 2、如果一个页面中全部都是HTML代码而没有需要解析的PHP语法,则没有必要保存为PHP文件,这样反而会降低运行效率。 3、如果是需要PHP控制HTML代码的输出,比如需要PHP判断用户是否登陆,如果登陆则输出A,未登录则...
  • &lt;div class="...保存二维码至本地相册&lt;/div&gt;  图片路径 data(){ return { orcode:require('./images/orcode2.jpg') //图片地址要require } }, js 代码 ...
  • linux下:后缀.so,前缀lib的文件为动态库 1.2 生成动态库 命令:gcc/g++   必选项的命令行参数: -shared==>生成动态库 -fPIC:生成与位置无关的代码 命令范式: gcc/g++
  • 动态代理和静态代理到底有什么区别,好处在哪里?
  • linux网络配置—将动态分配ip地址改为静态iplinux版本:centos一.配置流程1.输入 vim /etc/sysconfig/network-scripts/ifcfg-eth0 查看网卡信息BOOTPROTO=dhcp表示每次关闭linux系统在重新启动之后都会重新获得新的...
  • 用libqrencode库生成二维码并保存为BMP图片,需要准备: 二维码知识 + libqrencode.lib库或者源码 + BMP文件知识 + VS 二维码编码知识,可自动搜索,网上有很多。 libqrencode.lib库编译生成方法,可参见:...
  • 动态图转静态图 动态图有诸多优点,包括易用的接口,python风格的编程体验,友好的debug交互机制等。在动态图模式下,代码是按照我们编写的顺序依次执行。这种机制更符合Python程序员的习惯,可以很方便地将大脑中的...
  • 为什么要把动态网页以静态网页的形式发布呢?一个很重要的原因,就是因为搜索引擎。由于搜索引擎对aspx页面收录和html页面收录率的差别以及页面资源占用问题,我们很多时候需要实现ASPX页面动态静态。以目前互联网...
  • 动态站点和静态站点有什么区别

    千次阅读 2015-11-14 19:54:25
     静态网页是网站建设的基础,静态网页和动态网页之间也并不矛盾,为了网站适应搜索引擎检索的需要,即使采用动态网站技术,也可以将网页内容转化为静态网页发布。  动态网站也可以采用静动结合的原
  • 动态数组和静态数组

    千次阅读 2016-05-11 14:49:49
    动态数组和静态数组,可能在使用的时候看上去挺相似,实质却不一样。 现在我们就谈谈他们的区别 #include #include void fun( int *p ) { sizeof( p ) / sizeof( p[0] ); } int main( void ) { int a[5]; int *...
  • 动态路由和静态路由

    千次阅读 2014-09-14 11:02:42
    静态路由  静态路由是由管理员在路由器中手动配置的固定路由,路由明确地指定了包到达目的地必须经过的路径,除非网络管理员干预,否则静态路由不会发生变化。静态路由不能对网络的改变作出反应,所以一般说静态...
  • (1)静态网页是实实在在的保存在服务器上的文件,每一个网页都是一个独立的文件 (2)容易被搜索引擎搜索到 (3)静态网页没有数据库的支持,当网站信息量很大是完全依靠静态网页制作方式比较困难 (4)静态网页的...
  • 动态页面静态化之页面静态化方案

    千次阅读 2011-09-07 19:37:57
    动态页面静态化之页面静态...为什么都要把页面静态化呢?把页面静态化,好处有很多。例如:访问速度快,更有利于搜索引擎收录等。目前主流的静态化主要有两种:一种是通过程序将动态页面抓取并保存静态页面,这样的页
  • Unity3D中的动态字体和静态字体

    千次阅读 2019-02-26 02:54:12
    动态字体和静态字体区别在于,动态字体如果出现字体库中不存在的字体,会使用系统字体,而静态字体则不会,而且静态字体是图片,字体大小通过缩放来改变。Unity3D也有自带的字体,Windows下自带字体Arial。如果...
  • 二、为什么要用伪静态技术? Web应用程序最大的特点之一就是无状态,当一个页面跳转到另一个页面时,那么这个页面上的所有参数都将抛弃,所以动态页面一般利用url地址来保存其参数,就像: ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 651,520
精华内容 260,608
关键字:

为什么动态图保存变静态