fortplot_fast_io.f90 Source File


Source Code

module fortplot_fast_io
    !! Fast I/O operations for Windows CI performance optimization
    !!
    !! This module provides optimized file I/O operations that can switch between
    !! memory-backed and disk-based operations based on the execution environment.
    !! Addresses Issue #188: Slow test execution on Windows CI.
    
    use iso_fortran_env, only: int8, int32, int64, real64
    use fortplot_windows_performance, only: should_use_memory_backend, &
                                            get_performance_config, &
                                            performance_config_t
    use fortplot_memory_backend, only: memory_backend_t, get_memory_backend
    use fortplot_logging, only: log_debug, log_info
    implicit none
    private
    
    public :: fast_savefig
    public :: fast_file_exists
    public :: fast_file_size
    public :: enable_fast_io
    public :: disable_fast_io
    public :: get_fast_io_stats
    
    ! Module state
    logical, save :: fast_io_enabled = .false.
    integer, save :: memory_saves = 0
    integer, save :: disk_saves = 0
    real(real64), save :: total_memory_time = 0.0_real64
    real(real64), save :: total_disk_time = 0.0_real64
    
contains
    
    subroutine fast_savefig(fig, filename, use_memory_override)
        !! Fast savefig that can use memory backend when appropriate
        use fortplot_figure_core, only: figure_t
        ! render_figure is a type-bound procedure, not needed in import
        use fortplot_utils, only: get_backend_from_filename
        
        class(figure_t), intent(inout) :: fig
        character(len=*), intent(in) :: filename
        logical, intent(in), optional :: use_memory_override
        
        type(memory_backend_t), pointer :: mem_backend
        type(performance_config_t) :: perf_config
        logical :: use_memory
        real(real64) :: start_time, end_time
        character(len=20) :: backend_type
        integer(int8), dimension(:), allocatable :: buffer_data
        integer :: buffer_size
        
        ! Determine whether to use memory backend
        if (present(use_memory_override)) then
            use_memory = use_memory_override
        else
            use_memory = should_use_memory_backend() .and. fast_io_enabled
        end if
        
        call cpu_time(start_time)
        
        ! For now, always use disk-based savefig since render_figure is private
        ! Memory backend optimization needs to be reworked after refactoring
        call savefig_disk(fig, filename)
        disk_saves = disk_saves + 1
        
        call cpu_time(end_time)
        
        if (use_memory) then
            total_memory_time = total_memory_time + (end_time - start_time)
        else
            total_disk_time = total_disk_time + (end_time - start_time)
        end if
        
    end subroutine fast_savefig
    
    subroutine savefig_disk(fig, filename)
        !! Standard disk-based savefig (wrapper for original)
        ! savefig is a type-bound procedure, not needed in import
        use fortplot_figure_core, only: figure_t
        
        class(figure_t), intent(inout) :: fig
        character(len=*), intent(in) :: filename
        
        call fig%savefig(filename)
        
    end subroutine savefig_disk
    
    subroutine figure_to_buffer(fig, backend_type, buffer_data, buffer_size)
        !! Convert rendered figure to byte buffer
        use fortplot_figure_core, only: figure_t
        
        class(figure_t), intent(inout) :: fig
        character(len=*), intent(in) :: backend_type
        integer(int8), dimension(:), allocatable, intent(out) :: buffer_data
        integer, intent(out) :: buffer_size
        
        ! This is a simplified implementation
        ! In production, this would extract the rendered data from the backend
        ! For now, we'll create a minimal buffer as placeholder
        
        buffer_size = 0
        
        select case(trim(backend_type))
        case('png')
            call extract_png_buffer(fig, buffer_data, buffer_size)
        case('pdf')
            call extract_pdf_buffer(fig, buffer_data, buffer_size)
        case('ascii')
            call extract_ascii_buffer(fig, buffer_data, buffer_size)
        case default
            buffer_size = 0
        end select
        
    end subroutine figure_to_buffer
    
    subroutine extract_png_buffer(fig, buffer_data, buffer_size)
        !! Extract PNG data from figure backend
        use fortplot_figure_core, only: figure_t
        
        class(figure_t), intent(inout) :: fig
        integer(int8), dimension(:), allocatable, intent(out) :: buffer_data
        integer, intent(out) :: buffer_size
        
        ! For PNG backend, we would extract the bitmap data
        ! This requires accessing the backend's internal bitmap
        ! For now, create a minimal valid PNG header as placeholder
        
        buffer_size = 8  ! Minimal PNG signature
        allocate(buffer_data(buffer_size))
        
        ! PNG signature
        buffer_data(1:8) = [int(z'89', int8), int(z'50', int8), int(z'4E', int8), int(z'47', int8), &
                            int(z'0D', int8), int(z'0A', int8), int(z'1A', int8), int(z'0A', int8)]
        
    end subroutine extract_png_buffer
    
    subroutine extract_pdf_buffer(fig, buffer_data, buffer_size)
        !! Extract PDF data from figure backend
        use fortplot_figure_core, only: figure_t
        
        class(figure_t), intent(inout) :: fig
        integer(int8), dimension(:), allocatable, intent(out) :: buffer_data
        integer, intent(out) :: buffer_size
        
        ! For PDF backend, we would extract the PDF document
        ! For now, create a minimal PDF header as placeholder
        
        buffer_size = 9  ! Minimal PDF header
        allocate(buffer_data(buffer_size))
        
        ! PDF signature "%PDF-1.4\n"
        buffer_data(1:9) = [int(iachar('%'), int8), int(iachar('P'), int8), &
                            int(iachar('D'), int8), int(iachar('F'), int8), &
                            int(iachar('-'), int8), int(iachar('1'), int8), &
                            int(iachar('.'), int8), int(iachar('4'), int8), &
                            int(10, int8)]
        
    end subroutine extract_pdf_buffer
    
    subroutine extract_ascii_buffer(fig, buffer_data, buffer_size)
        !! Extract ASCII data from figure backend
        use fortplot_figure_core, only: figure_t
        
        class(figure_t), intent(inout) :: fig
        integer(int8), dimension(:), allocatable, intent(out) :: buffer_data
        integer, intent(out) :: buffer_size
        
        character(len=10) :: test_ascii = "ASCII_TEST"
        integer :: i
        
        buffer_size = len_trim(test_ascii)
        allocate(buffer_data(buffer_size))
        
        do i = 1, buffer_size
            buffer_data(i) = int(iachar(test_ascii(i:i)), int8)
        end do
        
    end subroutine extract_ascii_buffer
    
    function fast_file_exists(filename) result(exists)
        !! Fast file existence check (may use memory backend)
        character(len=*), intent(in) :: filename
        logical :: exists
        
        type(memory_backend_t), pointer :: mem_backend
        
        if (should_use_memory_backend() .and. fast_io_enabled) then
            mem_backend => get_memory_backend()
            exists = mem_backend%exists(filename)
            
            ! If not in memory, check disk
            if (.not. exists) then
                inquire(file=filename, exist=exists)
            end if
        else
            inquire(file=filename, exist=exists)
        end if
        
    end function fast_file_exists
    
    function fast_file_size(filename) result(size)
        !! Fast file size check (may use memory backend)
        character(len=*), intent(in) :: filename
        integer(int64) :: size
        
        type(memory_backend_t), pointer :: mem_backend
        integer :: iostat
        
        if (should_use_memory_backend() .and. fast_io_enabled) then
            mem_backend => get_memory_backend()
            size = mem_backend%get_size(filename)
            
            ! If not in memory, check disk
            if (size < 0) then
                inquire(file=filename, size=size, iostat=iostat)
                if (iostat /= 0) size = -1
            end if
        else
            inquire(file=filename, size=size, iostat=iostat)
            if (iostat /= 0) size = -1
        end if
        
    end function fast_file_size
    
    subroutine enable_fast_io()
        !! Enable fast I/O operations
        use fortplot_windows_performance, only: setup_windows_performance
        
        call setup_windows_performance()
        fast_io_enabled = .true.
        call log_info("Fast I/O enabled for Windows CI performance")
        
    end subroutine enable_fast_io
    
    subroutine disable_fast_io()
        !! Disable fast I/O operations
        fast_io_enabled = .false.
        call log_info("Fast I/O disabled")
        
    end subroutine disable_fast_io
    
    subroutine get_fast_io_stats(memory_count, disk_count, memory_time, disk_time)
        !! Get fast I/O statistics
        integer, intent(out), optional :: memory_count
        integer, intent(out), optional :: disk_count
        real(real64), intent(out), optional :: memory_time
        real(real64), intent(out), optional :: disk_time
        
        if (present(memory_count)) memory_count = memory_saves
        if (present(disk_count)) disk_count = disk_saves
        if (present(memory_time)) memory_time = total_memory_time
        if (present(disk_time)) disk_time = total_disk_time
        
    end subroutine get_fast_io_stats
    
end module fortplot_fast_io